diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..a2816621e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,132 @@ +############################### +# Core EditorConfig Options # +############################### +root = true +# All files +[*] +indent_style = space + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +############################### +# Naming Conventions # +############################### +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const +############################### +# C# Coding Conventions # +############################### +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true +############################### +# VB Coding Conventions # +############################### +[*.vb] +# Modifier preferences +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion \ No newline at end of file diff --git a/.github/workflows/catalog-api.yml b/.github/workflows/catalog-api.yml index e3e8e9afe..6def653d0 100644 --- a/.github/workflows/catalog-api.yml +++ b/.github/workflows/catalog-api.yml @@ -22,7 +22,7 @@ on: env: SERVICE: catalog-api IMAGE: catalog.api - DOTNET_VERSION: 6.0.x + DOTNET_VERSION: 7.0.x PROJECT_PATH: Services/Catalog/Catalog.API TESTS_PATH: Services/Catalog/Catalog.UnitTests diff --git a/.github/workflows/ordering-api.yml b/.github/workflows/ordering-api.yml index 7f6c3fff5..617fba8d7 100644 --- a/.github/workflows/ordering-api.yml +++ b/.github/workflows/ordering-api.yml @@ -22,7 +22,7 @@ on: env: SERVICE: ordering-api IMAGE: ordering.api - DOTNET_VERSION: 6.0.x + DOTNET_VERSION: 7.0.x PROJECT_PATH: Services/Ordering/Ordering.API TESTS_PATH: Services/Ordering/Ordering.UnitTests diff --git a/.gitignore b/.gitignore index 587a6e0a0..2810d6b1c 100644 --- a/.gitignore +++ b/.gitignore @@ -281,3 +281,5 @@ pub/ src/**/app.yaml src/**/inf.yaml +.angular/ +/src/Services/Identity/Identity.API/keys/*.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ccb80afb8..75ea5b017 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,7 @@ All contributions must be submitted as a [Pull Request (PR)](https://help.github The main branches are **`dev`** and **`master`**: - **`dev`**: Contains the latest code **and it is the branch actively developed**. -**All PRs must be against `dev` branch to be considered**. This branch is developed using `.NET 5` +**All PRs must be against `dev` branch to be considered**. This branch is developed using `.NET 7` - **`main`**: Synced from time to time from **`dev`**. It contains "stable" code.This branch contains changes specific to `.NET Core 3.1` (**Keep in mind "stable" does not mean PRODUCTION-READY!**) diff --git a/README.md b/README.md index 28c57eb6f..8498db06e 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,9 @@ In the future, more features will be implemented in the advanced scenario. **NEWS / ANNOUNCEMENTS** Do you want to be up-to-date on .NET Architecture guidance and reference apps like eShopOnContainers? --> Subscribe by "WATCHING" this new GitHub repo: https://github.com/dotnet-architecture/News -## Updated for .NET 6 +## Updated for .NET 7 -eShopOnContainers is updated to .NET 6 "wave" of technologies. Not just compilation but also new recommended code in EF Core, ASP.NET Core, and other new related versions with several significant changes. +eShopOnContainers is updated to .NET 7 "wave" of technologies. Not just compilation but also new recommended code in EF Core, ASP.NET Core, and other new related versions with several significant changes. **See more details in the [Release notes](https://github.com/dotnet-architecture/eShopOnContainers/wiki/Release-notes) wiki page**. @@ -86,7 +86,7 @@ eShopOnContainers is updated to .NET 6 "wave" of technologies. Not just compilat ### Architecture overview -This reference application is cross-platform at the server and client-side, thanks to .NET 6 services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS, or Windows/UWP plus any browser for the client web apps. +This reference application is cross-platform at the server and client-side, thanks to .NET 7 services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS, or Windows/UWP plus any browser for the client web apps. The architecture proposes a microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using HTTP as the communication protocol between the client apps and the microservices and supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus (a light message broker, to choose between RabbitMQ or Azure Service Bus, underneath) plus other features defined at the [roadmap](https://github.com/dotnet-architecture/eShopOnContainers/wiki/Roadmap). ![](img/eshop_logo.png) diff --git a/branch-guide.md b/branch-guide.md index 633dc8faf..decce0bc4 100644 --- a/branch-guide.md +++ b/branch-guide.md @@ -2,7 +2,8 @@ Following are the most important branches: -- `dev`: Contains the latest code **and it is the branch actively developed**. Note that **all PRs must be against the `dev` branch to be considered**. This branch is developed using `.NET 6` +- `dev`: Contains the latest code **and it is the branch actively developed**. Note that **all PRs must be against the `dev` branch to be considered**. This branch is developed using `.NET 7` +- `release/net-6`: Contains the code changes specific to the `.NET 6` - `release/net-5`: Contains the code changes specific to the `.NET 5` - `release/net-3.1.1`: Contains the code changes specific to the `.NET 3.1` diff --git a/deploy/k8s/helm/catalog-api/templates/configmap.yaml b/deploy/k8s/helm/catalog-api/templates/configmap.yaml index 292b9e9b9..0026ec5af 100644 --- a/deploy/k8s/helm/catalog-api/templates/configmap.yaml +++ b/deploy/k8s/helm/catalog-api/templates/configmap.yaml @@ -13,7 +13,7 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: - catalog__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.catalog.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }}; + catalog__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.catalog.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }}; catalog__PicBaseUrl: {{ $protocol }}://{{ $webshoppingapigw }}/c/api/v1/catalog/items/[0]/pic/ catalog__AzureStorageEnabled: "{{ .Values.inf.misc.useAzureStorage }}" all__EventBusConnection: {{ .Values.inf.eventbus.constr }} diff --git a/deploy/k8s/helm/identity-api/templates/configmap.yaml b/deploy/k8s/helm/identity-api/templates/configmap.yaml index 7c670f646..923b31a95 100644 --- a/deploy/k8s/helm/identity-api/templates/configmap.yaml +++ b/deploy/k8s/helm/identity-api/templates/configmap.yaml @@ -20,7 +20,7 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: - identity__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.identity.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }}; + identity__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.identity.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }}; identity__keystore: {{ .Values.inf.redis.keystore.constr }} all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}" mvc_e: http://{{ $mvc_url }} diff --git a/deploy/k8s/helm/inf.yaml b/deploy/k8s/helm/inf.yaml index 3e0b589c0..8ed680d6b 100644 --- a/deploy/k8s/helm/inf.yaml +++ b/deploy/k8s/helm/inf.yaml @@ -13,6 +13,7 @@ inf: user: sa # SQL user pwd: Pass@word # SQL pwd pid: Developer + TrustServerCertificate: true catalog: # inf.sql.catalog: settings for the catalog-api sql (user, pwd, db) db: CatalogDb # Catalog API SQL db name ordering: # inf.sql.ordering: settings for the ordering-api sql (user, pwd, db) diff --git a/deploy/k8s/helm/ordering-api/templates/configmap.yaml b/deploy/k8s/helm/ordering-api/templates/configmap.yaml index e93dddd5c..2363a2778 100644 --- a/deploy/k8s/helm/ordering-api/templates/configmap.yaml +++ b/deploy/k8s/helm/ordering-api/templates/configmap.yaml @@ -11,7 +11,7 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: - ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }}; + ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }}; urls__IdentityUrl: http://{{ .Values.app.svc.identity }} all__EventBusConnection: {{ .Values.inf.eventbus.constr }} all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}" diff --git a/deploy/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml b/deploy/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml index 7ed4a0e8e..2e20cfc2c 100644 --- a/deploy/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml +++ b/deploy/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml @@ -12,7 +12,7 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: - ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }}; + ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }}; ordering__EnableLoadTest: "{{ .Values.inf.misc.useLoadTest }}" all__EventBusConnection: {{ .Values.inf.eventbus.constr }} all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}" diff --git a/deploy/k8s/helm/webhooks-api/templates/configmap.yaml b/deploy/k8s/helm/webhooks-api/templates/configmap.yaml index 05b9b7f57..62ab64e05 100644 --- a/deploy/k8s/helm/webhooks-api/templates/configmap.yaml +++ b/deploy/k8s/helm/webhooks-api/templates/configmap.yaml @@ -13,7 +13,7 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: - webhooks__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.webhooks.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }}; + webhooks__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.webhooks.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }}; urls__IdentityUrl: http://{{ $identity }} urls__IdentityUrlExternal: {{ $protocol }}://{{ $identity }} all__EventBusConnection: {{ .Values.inf.eventbus.constr }} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile index aab4164b7..9a6b83be8 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles @@ -11,7 +11,6 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" -COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj" COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" @@ -33,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" @@ -42,6 +42,7 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj" COPY "docker-compose.dcproj" "docker-compose.dcproj" +COPY "Directory.Packages.props" "Directory.Packages.props" COPY "NuGet.config" "NuGet.config" RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln" diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop index 9a907ee69..22c8c38b7 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true @@ -6,7 +6,6 @@ EXPOSE 80 WORKDIR /src COPY ["src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj", "src/ApiGateways/Mobile.Bff.Shopping/aggregator/"] -COPY ["src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj", "src/BuildingBlocks/Devspaces.Support/"] COPY ["src/NuGet.config", "src/NuGet.config"] RUN dotnet restore src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503 diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs new file mode 100644 index 000000000..af98ca5c5 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs @@ -0,0 +1,62 @@ +internal static class Extensions +{ + public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration) + { + services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy")); + return services; + } + + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + services.AddHealthChecks() + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" }); + + return services; + } + + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + // Register delegating handlers + services.AddTransient(); + + // Register http services + services.AddHttpClient() + .AddHttpMessageHandler(); + + return services; + } + + public static IServiceCollection AddGrpcServices(this IServiceCollection services) + { + services.AddTransient(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var basketApi = services.GetRequiredService>().Value.GrpcBasket; + options.Address = new Uri(basketApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; + options.Address = new Uri(catalogApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; + options.Address = new Uri(orderingApi); + }).AddInterceptor(); + + return services; + } +} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs deleted file mode 100644 index 11473d1c1..000000000 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters -{ - namespace Basket.API.Infrastructure.Filters - { - public class AuthorizeCheckOperationFilter : IOperationFilter - { - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Check for authorize attribute - var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || - context.MethodInfo.GetCustomAttributes(true).OfType().Any(); - - if (!hasAuthorize) return; - - operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); - operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); - - var oAuthScheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } - }; - - operation.Security = new List - { - new() - { - [ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" } - } - }; - } - } - } -} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs index 881670f5e..549873d8d 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs @@ -1,34 +1,25 @@ global using CatalogApi; -global using Devspaces.Support; global using Grpc.Core.Interceptors; global using Grpc.Core; global using GrpcBasket; -global using GrpcOrdering; global using HealthChecks.UI.Client; global using Microsoft.AspNetCore.Authentication.JwtBearer; global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; -global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; -global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Microsoft.OpenApi.Models; -global using Serilog; -global using Swashbuckle.AspNetCore.SwaggerGen; global using System.Collections.Generic; global using System.IdentityModel.Tokens.Jwt; global using System.Linq; @@ -39,3 +30,5 @@ global using System.Text.Json; global using System.Threading.Tasks; global using System.Threading; global using System; +global using Microsoft.IdentityModel.Tokens; +global using Services.Common; diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs index c434074d3..ec159a0ea 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs @@ -28,7 +28,7 @@ public class GrpcExceptionInterceptor : Interceptor } catch (RpcException e) { - _logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message); + _logger.LogError(e, "Error calling via gRPC: {Status}", e.Status); return default; } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs deleted file mode 100644 index 24914ca33..000000000 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; - -public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler -{ - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; - - public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor, ILogger logger) - { - _httpContextAccessor = httpContextAccessor; - _logger = logger; - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - request.Version = new System.Version(2, 0); - request.Method = HttpMethod.Get; - - var authorizationHeader = _httpContextAccessor.HttpContext - .Request.Headers["Authorization"]; - - if (!string.IsNullOrEmpty(authorizationHeader)) - { - request.Headers.Add("Authorization", new List() { authorizationHeader }); - } - - var token = await GetToken(); - - if (token != null) - { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - } - - return await base.SendAsync(request, cancellationToken); - } - - async Task GetToken() - { - const string ACCESS_TOKEN = "access_token"; - - return await _httpContextAccessor.HttpContext - .GetTokenAsync(ACCESS_TOKEN); - } -} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj index 13c443025..35f37cb53 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Mobile.Shopping.HttpAggregator Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator ..\..\..\docker-compose.dcproj @@ -10,27 +10,30 @@ - + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - + diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs index d0686ef51..f7e807d8d 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs @@ -2,7 +2,6 @@ public class UpdateBasketItemsRequest { - public string BasketId { get; set; } public ICollection Updates { get; set; } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs index c5d6e10ff..91c5bca6b 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs @@ -1,23 +1,24 @@ -await BuildWebHost(args).RunAsync(); -IWebHost BuildWebHost(string[] args) => - WebHost - .CreateDefaultBuilder(args) - .ConfigureAppConfiguration(cb => - { - var sources = cb.Sources; - sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource() - { - Optional = true, - Path = "appsettings.localhost.json", - ReloadOnChange = false - }); - }) - .UseStartup() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) - .Build(); \ No newline at end of file +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddReverseProxy(builder.Configuration); +builder.Services.AddControllers(); + +builder.Services.AddHealthChecks(builder.Configuration); + +builder.Services.AddApplicationServices(); +builder.Services.AddGrpcServices(); + +builder.Services.Configure(builder.Configuration.GetSection("urls")); + +var app = builder.Build(); + +app.UseServiceDefaults(); + +app.UseHttpsRedirection(); + +app.MapControllers(); +app.MapReverseProxy(); + +await app.RunAsync(); diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json index c259d5094..925e70b0d 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json @@ -24,13 +24,6 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:61632/" - }, - "Azure Dev Spaces": { - "commandName": "AzureDevSpaces", - "launchBrowser": true, - "resourceGroup": "eshoptestedu", - "aksName": "eshoptestedu", - "subscriptionId": "e3035ac1-c06c-4daf-8939-57b3c5f1f759" } } } \ No newline at end of file diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs index e1c9a24bf..bf9c1b7e9 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs @@ -5,5 +5,4 @@ public interface IBasketService Task GetByIdAsync(string id); Task UpdateAsync(BasketData currentBasket); - } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs index 2a7de50a7..6d46a706f 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs @@ -2,10 +2,10 @@ public class OrderingService : IOrderingService { - private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; + private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; private readonly ILogger _logger; - public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger logger) + public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger logger) { _orderingGrpcClient = orderingGrpcClient; _logger = logger; @@ -48,14 +48,14 @@ public class OrderingService : IOrderingService return data; } - private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) + private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) { - var command = new CreateOrderDraftCommand + var command = new GrpcOrdering.CreateOrderDraftCommand { BuyerId = basketData.BuyerId, }; - basketData.Items.ForEach(i => command.Items.Add(new BasketItem + basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem { Id = i.Id, OldUnitPrice = (double)i.OldUnitPrice, diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs deleted file mode 100644 index 3f988395a..000000000 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs +++ /dev/null @@ -1,196 +0,0 @@ -namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) - .AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) - .AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) - .AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) - .AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); - - services.AddCustomMvc(Configuration) - .AddCustomAuthentication(Configuration) - .AddDevspaces() - .AddHttpServices() - .AddGrpcServices(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - var pathBase = Configuration["PATH_BASE"]; - - if (!string.IsNullOrEmpty(pathBase)) - { - loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseSwagger().UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); - - c.OAuthClientId("mobileshoppingaggswaggerui"); - c.OAuthClientSecret(string.Empty); - c.OAuthRealm(string.Empty); - c.OAuthAppName("Purchase BFF Swagger UI"); - }); - - app.UseRouting(); - app.UseCors("CorsPolicy"); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - }); - } -} - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) - { - services.AddOptions(); - services.Configure(configuration.GetSection("urls")); - - services.AddControllers() - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - - services.AddSwaggerGen(options => - { - options.DescribeAllEnumsAsStrings(); - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Shopping Aggregator for Mobile Clients", - Version = "v1", - Description = "Shopping Aggregator for Mobile Clients" - }); - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), - TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"), - - Scopes = new Dictionary() - { - { "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" } - } - } - } - }); - - options.OperationFilter(); - }); - - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .AllowAnyMethod() - .AllowAnyHeader() - .SetIsOriginAllowed((host) => true) - .AllowCredentials()); - }); - - return services; - } - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - - var identityUrl = configuration.GetValue("urls:identity"); - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - - }) - .AddJwtBearer(options => - { - options.Authority = identityUrl; - options.RequireHttpsMetadata = false; - options.Audience = "mobileshoppingagg"; - }); - - return services; - } - - public static IServiceCollection AddHttpServices(this IServiceCollection services) - { - //register delegating handlers - services.AddTransient(); - services.AddSingleton(); - - //register http services - - services.AddHttpClient() - .AddDevspacesSupport(); - - return services; - } - - public static IServiceCollection AddGrpcServices(this IServiceCollection services) - { - services.AddTransient(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var basketApi = services.GetRequiredService>().Value.GrpcBasket; - options.Address = new Uri(basketApi); - }).AddInterceptor(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; - options.Address = new Uri(catalogApi); - }).AddInterceptor(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; - options.Address = new Uri(orderingApi); - }).AddInterceptor(); - - return services; - } - -} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json index 26bb0ac7a..8526211d9 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json @@ -1,15 +1,138 @@ { "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "System.Net.Http": "Warning" + } + }, + "OpenApi": { + "Endpoint": { + "Name": "Purchase BFF V1" + }, + "Document": { + "Description": "Shopping Aggregator for Mobile Clients", + "Title": "Shopping Aggregator for Mobile Clients", + "Version": "v1" + }, + "Auth": { + "ClientId": "mobileshoppingaggswaggerui", + "AppName": "Mobile shopping BFF Swagger UI" + } + }, + "Identity": { + "Url": "http://localhost:5223", + "Audience": "mobileshoppingagg", + "Scopes": { + "webshoppingagg": "Shopping Aggregator for Mobile Clients" + } + }, + "ReverseProxy": { + "Routes": { + "c-short": { + "ClusterId": "catalog", + "Match": { + "Path": "c/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/c" } + ] + }, + "c-long": { + "ClusterId": "catalog", + "Match": { + "Path": "catalog-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/catalog-api" } + ] + }, + "b-short": { + "ClusterId": "basket", + "Match": { + "Path": "b/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/b" } + ] + }, + "b-long": { + "ClusterId": "basket", + "Match": { + "Path": "basket-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/basket-api" } + ] + }, + "o-short": { + "ClusterId": "orders", + "Match": { + "Path": "o/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/o" } + ] + }, + "o-long": { + "ClusterId": "orders", + "Match": { + "Path": "ordering-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/ordering-api" } + ] + }, + "h-long": { + "ClusterId": "signalr", + "Match": { + "Path": "hub/notificationhub/{**catch-all}" + } } }, - "Console": { - "LogLevel": { - "Default": "Warning" + "Clusters": { + "basket": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5221" + } + } + }, + "catalog": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5222" + } + } + }, + "orders": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5224" + } + } + }, + "signalr": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5225" + } + } } } - } + }, + "Urls": { + "Basket": "http://localhost:5221", + "Catalog": "http://localhost:5222", + "Orders": "http://localhost:5224", + "Identity": "http://localhost:5223", + "Signalr": "http://localhost:5225", + "GrpcBasket": "http://localhost:6221", + "GrpcCatalog": "http://localhost:6222", + "GrpcOrdering": "http://localhost:6224" + }, + "CatalogUrlHC": "http://localhost:5222/hc", + "OrderingUrlHC": "http://localhost:5224/hc", + "BasketUrlHC": "http://localhost:5221/hc", + "IdentityUrlHC": "http://localhost:5223/hc" } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json index 86fd1541d..ce1d6cfbd 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json @@ -8,16 +8,19 @@ "grpcCatalog": "http://localhost:81", "grpcOrdering": "http://localhost:5581" }, - "IdentityUrlExternal": "http://localhost:5105", - "IdentityUrl": "http://localhost:5105", + "Identity": { + "ExternalUrl": "http://localhost:5105", + "Url": "http://localhost:5105", + }, "Logging": { - "IncludeScopes": false, "Debug": { + "IncludeScopes": false, "LogLevel": { "Default": "Debug" } }, "Console": { + "IncludeScopes": false, "LogLevel": { "Default": "Debug" } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml deleted file mode 100644 index 8dbac7128..000000000 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml +++ /dev/null @@ -1,55 +0,0 @@ -kind: helm-release -apiVersion: 1.1 -build: - context: ..\..\..\.. - dockerfile: Dockerfile -install: - chart: ../../../../k8s/helm/mobileshoppingagg - set: - image: - tag: $(tag) - pullPolicy: Never - ingress: - annotations: - kubernetes.io/ingress.class: traefik-azds - hosts: - # This expands to [space.s.]apigwms...aksapp.io - - $(spacePrefix)eshop$(hostSuffix) - inf: - k8s: - dns: $(spacePrefix)eshop$(hostSuffix) - values: - - values.dev.yaml? - - secrets.dev.yaml? - - app.yaml - - inf.yaml -configurations: - develop: - build: - useGitIgnore: true - dockerfile: Dockerfile.develop - container: - syncTarget: /src - sync: - - '**/Pages/**' - - '**/Views/**' - - '**/wwwroot/**' - - '!**/*.{sln,csproj}' - command: - - dotnet - - run - - --no-restore - - --no-build - - --no-launch-profile - - -c - - ${Configuration:-Debug} - iterate: - processesToKill: - - dotnet - - vsdbg - buildCommands: - - - dotnet - - build - - --no-restore - - -c - - ${Configuration:-Debug} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/HomeController.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/HomeController.cs deleted file mode 100644 index 55df5880b..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/HomeController.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers; - -[Route("")] -public class HomeController : Controller -{ - [HttpGet] - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } -} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile index 8761763a1..e8bd952a2 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles @@ -11,7 +11,6 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" -COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj" COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" @@ -33,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" @@ -42,6 +42,7 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj" COPY "docker-compose.dcproj" "docker-compose.dcproj" +COPY "Directory.Packages.props" "Directory.Packages.props" COPY "NuGet.config" "NuGet.config" RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln" diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop index 268a9c3ba..26c5b4599 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true @@ -6,7 +6,6 @@ EXPOSE 80 WORKDIR /src COPY ["src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj", "src/ApiGateways/Web.Bff.Shopping/aggregator/"] -COPY ["src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj", "src/BuildingBlocks/Devspaces.Support/"] COPY ["src/NuGet.config", "src/NuGet.config"] RUN dotnet restore src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503 diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Extensions/Extensions.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Extensions/Extensions.cs new file mode 100644 index 000000000..fefb4f351 --- /dev/null +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Extensions/Extensions.cs @@ -0,0 +1,63 @@ +internal static class Extensions +{ + public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration) + { + services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy")); + + return services; + } + + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + services.AddHealthChecks() + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" }); + + return services; + } + + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + // Register delegating handlers + services.AddTransient(); + + // Register http services + services.AddHttpClient() + .AddHttpMessageHandler(); + + return services; + } + + public static IServiceCollection AddGrpcServices(this IServiceCollection services) + { + services.AddTransient(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var basketApi = services.GetRequiredService>().Value.GrpcBasket; + options.Address = new Uri(basketApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; + options.Address = new Uri(catalogApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; + options.Address = new Uri(orderingApi); + }).AddInterceptor(); + + return services; + } +} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs deleted file mode 100644 index 99bf07048..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters -{ - namespace Basket.API.Infrastructure.Filters - { - public class AuthorizeCheckOperationFilter : IOperationFilter - { - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Check for authorize attribute - var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || - context.MethodInfo.GetCustomAttributes(true).OfType().Any(); - - if (!hasAuthorize) return; - - operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); - operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); - - var oAuthScheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } - }; - - operation.Security = new List - { - new() - { - [ oAuthScheme ] = new[] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" } - } - }; - } - } - } - -} \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs index 6162c557e..97b9e1222 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs @@ -1,41 +1,27 @@ -global using CatalogApi; -global using Devspaces.Support; -global using Grpc.Core.Interceptors; +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Net; +global using System.Net.Http; +global using System.Net.Http.Headers; +global using System.Text.Json; +global using System.Threading; +global using System.Threading.Tasks; +global using CatalogApi; global using Grpc.Core; +global using Grpc.Core.Interceptors; global using GrpcBasket; -global using GrpcOrdering; -global using HealthChecks.UI.Client; -global using Microsoft.AspNetCore.Authentication.JwtBearer; global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; -global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; -global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; -global using Microsoft.OpenApi.Models; -global using Serilog; -global using Swashbuckle.AspNetCore.SwaggerGen; -global using System.Collections.Generic; -global using System.IdentityModel.Tokens.Jwt; -global using System.Linq; -global using System.Net.Http.Headers; -global using System.Net.Http; -global using System.Net; -global using System.Text.Json; -global using System.Threading.Tasks; -global using System.Threading; -global using System; +global using Services.Common; diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs index 20adb2fc7..9611c6177 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs @@ -28,7 +28,7 @@ public class GrpcExceptionInterceptor : Interceptor } catch (RpcException e) { - _logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message); + _logger.LogError(e, "Error calling via gRPC: {Status}", e.Status); return default; } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs deleted file mode 100644 index d9b3b0ee1..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure; - -public class HttpClientAuthorizationDelegatingHandler - : DelegatingHandler -{ - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var authorizationHeader = _httpContextAccessor.HttpContext - .Request.Headers["Authorization"]; - - if (!string.IsNullOrWhiteSpace(authorizationHeader)) - { - request.Headers.Add("Authorization", new List() { authorizationHeader }); - } - - var token = await GetTokenAsync(); - - if (token != null) - { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - } - - return await base.SendAsync(request, cancellationToken); - } - - Task GetTokenAsync() - { - const string ACCESS_TOKEN = "access_token"; - - return _httpContextAccessor.HttpContext - .GetTokenAsync(ACCESS_TOKEN); - } -} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs index 70506fbda..6bb3a0d3b 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs @@ -1,24 +1,38 @@ -await BuildWebHost(args).RunAsync(); - -IWebHost BuildWebHost(string[] args) => - WebHost - .CreateDefaultBuilder(args) - .ConfigureAppConfiguration(cb => - { - var sources = cb.Sources; - sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource() - { - Optional = true, - Path = "appsettings.localhost.json", - ReloadOnChange = false - }); - }) - .UseStartup() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) - .Build(); \ No newline at end of file +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddReverseProxy(builder.Configuration); +builder.Services.AddControllers(); + +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddCors(options => +{ + // TODO: Read allowed origins from configuration + options.AddPolicy("CorsPolicy", + builder => builder + .SetIsOriginAllowed((host) => true) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); +}); + +builder.Services.AddApplicationServices(); +builder.Services.AddGrpcServices(); + +builder.Services.Configure(builder.Configuration.GetSection("urls")); + +var app = builder.Build(); + +app.UseServiceDefaults(); + +app.UseHttpsRedirection(); + +app.UseCors("CorsPolicy"); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); +app.MapReverseProxy(); + +await app.RunAsync(); diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json index 925e70b0d..4525154b7 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json @@ -1,29 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:57425/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "api/values", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "PurchaseForMvc": { + "Web.Shopping.HttpAggregator": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "api/values", + "applicationUrl": "http://localhost:5229/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:61632/" + } } } } \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs index 41d14d450..a58c6a903 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs @@ -10,7 +10,7 @@ public class BasketService : IBasketService _basketClient = basketClient; _logger = logger; } - + public async Task GetByIdAsync(string id) { _logger.LogDebug("grpc client created, request = {@id}", id); diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs index afa86b31b..c9398179f 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs @@ -2,10 +2,10 @@ public class OrderingService : IOrderingService { - private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; + private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; private readonly ILogger _logger; - public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger logger) + public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger logger) { _orderingGrpcClient = orderingGrpcClient; _logger = logger; @@ -48,14 +48,14 @@ public class OrderingService : IOrderingService return data; } - private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) + private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) { - var command = new CreateOrderDraftCommand + var command = new GrpcOrdering.CreateOrderDraftCommand { BuyerId = basketData.BuyerId, }; - basketData.Items.ForEach(i => command.Items.Add(new BasketItem + basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem { Id = i.Id, OldUnitPrice = (double)i.OldUnitPrice, diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs deleted file mode 100644 index 6e8e66931..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs +++ /dev/null @@ -1,199 +0,0 @@ -namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) - .AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) - .AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) - .AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) - .AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); - - services.AddCustomMvc(Configuration) - .AddCustomAuthentication(Configuration) - .AddDevspaces() - .AddApplicationServices() - .AddGrpcServices(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - var pathBase = Configuration["PATH_BASE"]; - if (!string.IsNullOrEmpty(pathBase)) - { - loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - - app.UseSwagger().UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); - - c.OAuthClientId("webshoppingaggswaggerui"); - c.OAuthClientSecret(string.Empty); - c.OAuthRealm(string.Empty); - c.OAuthAppName("web shopping bff Swagger UI"); - }); - - app.UseRouting(); - app.UseCors("CorsPolicy"); - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - }); - } -} - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - - var identityUrl = configuration.GetValue("urls:identity"); - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - - }) - .AddJwtBearer(options => - { - options.Authority = identityUrl; - options.RequireHttpsMetadata = false; - options.Audience = "webshoppingagg"; - }); - - return services; - } - - public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) - { - services.AddOptions(); - services.Configure(configuration.GetSection("urls")); - - services.AddControllers() - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - - services.AddSwaggerGen(options => - { - options.DescribeAllEnumsAsStrings(); - - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Shopping Aggregator for Web Clients", - Version = "v1", - Description = "Shopping Aggregator for Web Clients" - }); - - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), - TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"), - - Scopes = new Dictionary() - { - { "webshoppingagg", "Shopping Aggregator for Web Clients" } - } - } - } - }); - - options.OperationFilter(); - }); - - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .SetIsOriginAllowed((host) => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); - }); - - return services; - } - public static IServiceCollection AddApplicationServices(this IServiceCollection services) - { - //register delegating handlers - services.AddTransient(); - services.AddSingleton(); - - //register http services - - services.AddHttpClient() - .AddHttpMessageHandler() - .AddDevspacesSupport(); - - return services; - } - - public static IServiceCollection AddGrpcServices(this IServiceCollection services) - { - services.AddTransient(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var basketApi = services.GetRequiredService>().Value.GrpcBasket; - options.Address = new Uri(basketApi); - }).AddInterceptor(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; - options.Address = new Uri(catalogApi); - }).AddInterceptor(); - - services.AddScoped(); - - services.AddGrpcClient((services, options) => - { - var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; - options.Address = new Uri(orderingApi); - }).AddInterceptor(); - - return services; - } -} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj index 921d5b709..d7523af19 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj @@ -1,37 +1,22 @@  - net6.0 + net7.0 Web.Shopping.HttpAggregator Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator ..\..\..\docker-compose.dcproj - false - true - + + + + + - - - - - - - - - - - - - - - - - - + diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json index 19b8c1529..b0bacf428 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json @@ -1,15 +1,8 @@ { "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Debug" - } - }, - "Console": { - "LogLevel": { - "Default": "Debug" - } + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" } } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json index 26bb0ac7a..711da6143 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json @@ -1,15 +1,138 @@ { "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "System.Net.Http": "Warning" + } + }, + "OpenApi": { + "Endpoint": { + "Name": "Purchase BFF V1" + }, + "Document": { + "Description": "Shopping Aggregator for Web Clients", + "Title": "Shopping Aggregator for Web Clients", + "Version": "v1" + }, + "Auth": { + "ClientId": "webshoppingaggswaggerui", + "AppName": "Web Shopping BFF Swagger UI" + } + }, + "Identity": { + "Url": "http://localhost:5223", + "Audience": "webshoppingagg", + "Scopes": { + "webshoppingagg": "Shopping Aggregator for Web Clients" + } + }, + "ReverseProxy": { + "Routes": { + "c-short": { + "ClusterId": "catalog", + "Match": { + "Path": "c/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/c" } + ] + }, + "c-long": { + "ClusterId": "catalog", + "Match": { + "Path": "catalog-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/catalog-api" } + ] + }, + "b-short": { + "ClusterId": "basket", + "Match": { + "Path": "b/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/b" } + ] + }, + "b-long": { + "ClusterId": "basket", + "Match": { + "Path": "basket-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/basket-api" } + ] + }, + "o-short": { + "ClusterId": "orders", + "Match": { + "Path": "o/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/o" } + ] + }, + "o-long": { + "ClusterId": "orders", + "Match": { + "Path": "ordering-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/ordering-api" } + ] + }, + "h-long": { + "ClusterId": "signalr", + "Match": { + "Path": "hub/notificationhub/{**catch-all}" + } } }, - "Console": { - "LogLevel": { - "Default": "Warning" + "Clusters": { + "basket": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5221" + } + } + }, + "catalog": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5222" + } + } + }, + "orders": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5224" + } + } + }, + "signalr": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5225" + } + } } } - } + }, + "Urls": { + "Basket": "http://localhost:5221", + "Catalog": "http://localhost:5222", + "Orders": "http://localhost:5224", + "Identity": "http://localhost:5223", + "Signalr": "http://localhost:5225", + "GrpcBasket": "http://localhost:6221", + "GrpcCatalog": "http://localhost:6222", + "GrpcOrdering": "http://localhost:6224" + }, + "CatalogUrlHC": "http://localhost:5222/hc", + "OrderingUrlHC": "http://localhost:5224/hc", + "BasketUrlHC": "http://localhost:5221/hc", + "IdentityUrlHC": "http://localhost:5223/hc" } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json deleted file mode 100644 index 055bcfc7f..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "urls": { - "basket": "http://localhost:55103", - "catalog": "http://localhost:55101", - "orders": "http://localhost:55102", - "identity": "http://localhost:55105", - "grpcBasket": "http://localhost:5580", - "grpcCatalog": "http://localhost:81", - "grpcOrdering": "http://localhost:5581" - } -} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/azds.yaml b/src/ApiGateways/Web.Bff.Shopping/aggregator/azds.yaml deleted file mode 100644 index 189d2261d..000000000 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/azds.yaml +++ /dev/null @@ -1,55 +0,0 @@ -kind: helm-release -apiVersion: 1.1 -build: - context: ..\..\..\.. - dockerfile: Dockerfile -install: - chart: ../../../../k8s/helm/webshoppingagg - set: - image: - tag: $(tag) - pullPolicy: Never - ingress: - annotations: - kubernetes.io/ingress.class: traefik-azds - hosts: - # This expands to [space.s.]apigwms...aksapp.io - - $(spacePrefix)eshop$(hostSuffix) - inf: - k8s: - dns: $(spacePrefix)eshop$(hostSuffix) - values: - - values.dev.yaml? - - secrets.dev.yaml? - - app.yaml - - inf.yaml -configurations: - develop: - build: - useGitIgnore: true - dockerfile: Dockerfile.develop - container: - syncTarget: /src - sync: - - '**/Pages/**' - - '**/Views/**' - - '**/wwwroot/**' - - '!**/*.{sln,csproj}' - command: - - dotnet - - run - - --no-restore - - --no-build - - --no-launch-profile - - -c - - ${Configuration:-Debug} - iterate: - processesToKill: - - dotnet - - vsdbg - buildCommands: - - - dotnet - - build - - --no-restore - - -c - - ${Configuration:-Debug} diff --git a/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj b/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj deleted file mode 100644 index 3398f17e6..000000000 --- a/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - net6.0 - - - - - - - diff --git a/src/BuildingBlocks/Devspaces.Support/DevspacesMessageHandler.cs b/src/BuildingBlocks/Devspaces.Support/DevspacesMessageHandler.cs deleted file mode 100644 index ac054693f..000000000 --- a/src/BuildingBlocks/Devspaces.Support/DevspacesMessageHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Devspaces.Support; - -public class DevspacesMessageHandler : DelegatingHandler -{ - private const string DevspacesHeaderName = "azds-route-as"; - private readonly IHttpContextAccessor _httpContextAccessor; - public DevspacesMessageHandler(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var req = _httpContextAccessor.HttpContext.Request; - - if (req.Headers.ContainsKey(DevspacesHeaderName)) - { - request.Headers.Add(DevspacesHeaderName, req.Headers[DevspacesHeaderName] as IEnumerable); - } - return base.SendAsync(request, cancellationToken); - } -} diff --git a/src/BuildingBlocks/Devspaces.Support/GlobalUsings.cs b/src/BuildingBlocks/Devspaces.Support/GlobalUsings.cs deleted file mode 100644 index 06c07e8fb..000000000 --- a/src/BuildingBlocks/Devspaces.Support/GlobalUsings.cs +++ /dev/null @@ -1,6 +0,0 @@ -global using Microsoft.AspNetCore.Http; -global using Microsoft.Extensions.DependencyInjection; -global using System.Collections.Generic; -global using System.Net.Http; -global using System.Threading.Tasks; -global using System.Threading; diff --git a/src/BuildingBlocks/Devspaces.Support/HttpClientBuilderDevspacesExtensions.cs b/src/BuildingBlocks/Devspaces.Support/HttpClientBuilderDevspacesExtensions.cs deleted file mode 100644 index fd78b9a40..000000000 --- a/src/BuildingBlocks/Devspaces.Support/HttpClientBuilderDevspacesExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Devspaces.Support; - -public static class HttpClientBuilderDevspacesExtensions -{ - public static IHttpClientBuilder AddDevspacesSupport(this IHttpClientBuilder builder) - { - builder.AddHttpMessageHandler(); - return builder; - } -} diff --git a/src/BuildingBlocks/Devspaces.Support/ServiceCollectionDevspacesExtensions.cs b/src/BuildingBlocks/Devspaces.Support/ServiceCollectionDevspacesExtensions.cs deleted file mode 100644 index 15c3b1515..000000000 --- a/src/BuildingBlocks/Devspaces.Support/ServiceCollectionDevspacesExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Devspaces.Support; - -public static class ServiceCollectionDevspacesExtensions -{ - public static IServiceCollection AddDevspaces(this IServiceCollection services) - { - services.AddTransient(); - return services; - } -} diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj b/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj index 7b75841ec..415ba11bc 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj @@ -1,16 +1,18 @@  - net6.0 + net7.0 + false + false - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs index 13ae99afa..5309a4a9f 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs @@ -1,5 +1,4 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -using System; using System.Linq; using Xunit; @@ -18,7 +17,7 @@ namespace EventBus.Tests public void After_One_Event_Subscription_Should_Contain_The_Event() { var manager = new InMemoryEventBusSubscriptionsManager(); - manager.AddSubscription(); + manager.AddSubscription(); Assert.True(manager.HasSubscriptionsForEvent()); } diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs index 4e85e7ae6..63496664f 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs @@ -1,7 +1,4 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -using System; -using System.Collections.Generic; -using System.Text; namespace EventBus.Tests { diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs index 72e1ed2cd..b82ac6a5f 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs @@ -1,7 +1,4 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; namespace EventBus.Tests @@ -15,9 +12,10 @@ namespace EventBus.Tests Handled = false; } - public async Task Handle(TestIntegrationEvent @event) + public Task Handle(TestIntegrationEvent @event) { Handled = true; + return Task.CompletedTask; } } } diff --git a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs index 0b5b793ee..82ea25baa 100644 --- a/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs +++ b/src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs @@ -1,7 +1,4 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; namespace EventBus.Tests @@ -15,9 +12,10 @@ namespace EventBus.Tests Handled = false; } - public async Task Handle(TestIntegrationEvent @event) + public Task Handle(TestIntegrationEvent @event) { Handled = true; + return Task.CompletedTask; } } } diff --git a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs index 492a10e42..cab6338e8 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs @@ -8,12 +8,6 @@ public interface IEventBus where T : IntegrationEvent where TH : IIntegrationEventHandler; - void SubscribeDynamic(string eventName) - where TH : IDynamicIntegrationEventHandler; - - void UnsubscribeDynamic(string eventName) - where TH : IDynamicIntegrationEventHandler; - void Unsubscribe() where TH : IIntegrationEventHandler where T : IntegrationEvent; diff --git a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj index 37396d3ec..6d33cff34 100644 --- a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.eShopOnContainers.BuildingBlocks.EventBus diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs index 31118c4da..5c53b8c48 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs @@ -1,7 +1,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; public record IntegrationEvent -{ +{ public IntegrationEvent() { Id = Guid.NewGuid(); diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs index 48714cd2f..4f3f573f5 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs @@ -59,7 +59,7 @@ public class DefaultRabbitMQPersistentConnection .Or() .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { - _logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message); + _logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s", $"{time.TotalSeconds:n1}"); } ); @@ -81,7 +81,7 @@ public class DefaultRabbitMQPersistentConnection } else { - _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened"); + _logger.LogCritical("Fatal error: RabbitMQ connections could not be created and opened"); return false; } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 2721bf09f..aa4dc17ef 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -1,28 +1,28 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; +using Microsoft.Extensions.DependencyInjection; public class EventBusRabbitMQ : IEventBus, IDisposable { const string BROKER_NAME = "eshop_event_bus"; - const string AUTOFAC_SCOPE_NAME = "eshop_event_bus"; private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly ILogger _logger; private readonly IEventBusSubscriptionsManager _subsManager; - private readonly ILifetimeScope _autofac; + private readonly IServiceProvider _serviceProvider; private readonly int _retryCount; private IModel _consumerChannel; private string _queueName; public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger, - ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager, string queueName = null, int retryCount = 5) + IServiceProvider serviceProvider, IEventBusSubscriptionsManager subsManager, string queueName = null, int retryCount = 5) { _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _queueName = queueName; _consumerChannel = CreateConsumerChannel(); - _autofac = autofac; + _serviceProvider = serviceProvider; _retryCount = retryCount; _subsManager.OnEventRemoved += SubsManager_OnEventRemoved; } @@ -57,7 +57,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable .Or() .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { - _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message); + _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s", @event.Id, $"{time.TotalSeconds:n1}"); }); var eventName = @event.GetType().Name; @@ -79,7 +79,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable var properties = channel.CreateBasicProperties(); properties.DeliveryMode = 2; // persistent - _logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id); + _logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id); channel.BasicPublish( exchange: BROKER_NAME, @@ -122,7 +122,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable { _persistentConnection.TryConnect(); } - + _consumerChannel.QueueBind(queue: _queueName, exchange: BROKER_NAME, routingKey: eventName); @@ -193,7 +193,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable } catch (Exception ex) { - _logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message); + _logger.LogWarning(ex, "Error Processing message \"{Message}\"", message); } // Even on exception we take the message off the queue. @@ -240,20 +240,20 @@ public class EventBusRabbitMQ : IEventBus, IDisposable if (_subsManager.HasSubscriptionsForEvent(eventName)) { - await using var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME); + await using var scope = _serviceProvider.CreateAsyncScope(); var subscriptions = _subsManager.GetHandlersForEvent(eventName); foreach (var subscription in subscriptions) { if (subscription.IsDynamic) { - if (scope.ResolveOptional(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue; + if (scope.ServiceProvider.GetService(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue; using dynamic eventData = JsonDocument.Parse(message); await Task.Yield(); await handler.Handle(eventData); } else { - var handler = scope.ResolveOptional(subscription.HandlerType); + var handler = scope.ServiceProvider.GetService(subscription.HandlerType); if (handler == null) continue; var eventType = _subsManager.GetEventTypeByName(eventName); var integrationEvent = JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj index b6b23483c..329cc99e1 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj @@ -1,16 +1,15 @@  - net6.0 + net7.0 Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ - - - - - + + + + diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs index 6fa5f0bf4..d63437f0e 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs @@ -7,7 +7,6 @@ global using RabbitMQ.Client.Exceptions; global using System; global using System.IO; global using System.Net.Sockets; -global using Autofac; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/DefaultServiceBusPersisterConnection.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/DefaultServiceBusPersisterConnection.cs index edaed7455..502a8cc8c 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/DefaultServiceBusPersisterConnection.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/DefaultServiceBusPersisterConnection.cs @@ -27,7 +27,7 @@ public class DefaultServiceBusPersisterConnection : IServiceBusPersisterConnecti } } - public ServiceBusAdministrationClient AdministrationClient => + public ServiceBusAdministrationClient AdministrationClient => _subscriptionClient; public ServiceBusClient CreateModel() diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs index b1dce9917..10abbfafc 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.DependencyInjection; + namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; public class EventBusServiceBus : IEventBus, IAsyncDisposable @@ -5,21 +7,20 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable private readonly IServiceBusPersisterConnection _serviceBusPersisterConnection; private readonly ILogger _logger; private readonly IEventBusSubscriptionsManager _subsManager; - private readonly ILifetimeScope _autofac; + private readonly IServiceProvider _serviceProvider; private readonly string _topicName = "eshop_event_bus"; private readonly string _subscriptionName; private readonly ServiceBusSender _sender; private readonly ServiceBusProcessor _processor; - private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus"; private const string INTEGRATION_EVENT_SUFFIX = "IntegrationEvent"; public EventBusServiceBus(IServiceBusPersisterConnection serviceBusPersisterConnection, - ILogger logger, IEventBusSubscriptionsManager subsManager, ILifetimeScope autofac, string subscriptionClientName) + ILogger logger, IEventBusSubscriptionsManager subsManager, IServiceProvider serviceProvider, string subscriptionClientName) { _serviceBusPersisterConnection = serviceBusPersisterConnection; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); - _autofac = autofac; + _serviceProvider = serviceProvider; _subscriptionName = subscriptionClientName; _sender = _serviceBusPersisterConnection.TopicClient.CreateSender(_topicName); ServiceBusProcessorOptions options = new ServiceBusProcessorOptions { MaxConcurrentCalls = 10, AutoCompleteMessages = false }; @@ -139,7 +140,7 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable var ex = args.Exception; var context = args.ErrorSource; - _logger.LogError(ex, "ERROR handling message: {ExceptionMessage} - Context: {@ExceptionContext}", ex.Message, context); + _logger.LogError(ex, "Error handling message - Context: {@ExceptionContext}", context); return Task.CompletedTask; } @@ -149,20 +150,20 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable var processed = false; if (_subsManager.HasSubscriptionsForEvent(eventName)) { - var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME); + await using var scope = _serviceProvider.CreateAsyncScope(); var subscriptions = _subsManager.GetHandlersForEvent(eventName); foreach (var subscription in subscriptions) { if (subscription.IsDynamic) { - if (scope.ResolveOptional(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue; + if (scope.ServiceProvider.GetService(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue; using dynamic eventData = JsonDocument.Parse(message); await handler.Handle(eventData); } else { - var handler = scope.ResolveOptional(subscription.HandlerType); + var handler = scope.ServiceProvider.GetService(subscription.HandlerType); if (handler == null) continue; var eventType = _subsManager.GetEventTypeByName(eventName); var integrationEvent = JsonSerializer.Deserialize(message, eventType); @@ -196,4 +197,4 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable _subsManager.Clear(); await _processor.CloseAsync(); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj index e725de1c7..8b1785e6f 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj @@ -1,15 +1,14 @@  - net6.0 + net7.0 Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus - - - - + + + diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs index b0465794f..836f5ae52 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs @@ -1,19 +1,13 @@ global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -global using static Microsoft.eShopOnContainers.BuildingBlocks.EventBus.InMemoryEventBusSubscriptionsManager; -global using System.Collections.Generic; -global using System.Linq; -global using System.Text.Json.Serialization; global using System.Threading.Tasks; global using System; -global using Autofac; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; global using Microsoft.Extensions.Logging; global using System.Text; global using System.Text.Json; global using Azure.Messaging.ServiceBus; global using Azure.Messaging.ServiceBus.Administration; -global using System; diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj index c68db458e..356a583fb 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj @@ -1,18 +1,18 @@  - net6.0 + net7.0 Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs index 90826f22e..7da4b88ee 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs @@ -7,7 +7,7 @@ public class IntegrationEventLogEntry { EventId = @event.Id; CreationTime = @event.CreationDate; - EventTypeName = @event.GetType().FullName; + EventTypeName = @event.GetType().FullName; Content = JsonSerializer.Serialize(@event, @event.GetType(), new JsonSerializerOptions { WriteIndented = true @@ -29,7 +29,7 @@ public class IntegrationEventLogEntry public string TransactionId { get; private set; } public IntegrationEventLogEntry DeserializeJsonContent(Type type) - { + { IntegrationEvent = JsonSerializer.Deserialize(Content, type, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }) as IntegrationEvent; return this; } diff --git a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj index 5c3ba6721..4b1055ace 100644 --- a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj +++ b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 false @@ -10,17 +10,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + diff --git a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs index 3c7fc105a..45bedc2c4 100644 --- a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs +++ b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs @@ -1,30 +1,30 @@ -using Microsoft.EntityFrameworkCore; +using System; +using System.Data.SqlClient; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Polly; -using System; -using System.Data.SqlClient; namespace Microsoft.AspNetCore.Hosting { public static class IWebHostExtensions { - public static bool IsInKubernetes(this IWebHost webHost) + public static bool IsInKubernetes(this IServiceProvider services) { - var cfg = webHost.Services.GetService(); + var cfg = services.GetService(); var orchestratorType = cfg.GetValue("OrchestratorType"); return orchestratorType?.ToUpper() == "K8S"; } - public static IWebHost MigrateDbContext(this IWebHost webHost, Action seeder) where TContext : DbContext + public static IServiceProvider MigrateDbContext(this IServiceProvider services, Action seeder) where TContext : DbContext { - var underK8s = webHost.IsInKubernetes(); + var underK8s = services.IsInKubernetes(); - using var scope = webHost.Services.CreateScope(); - var services = scope.ServiceProvider; - var logger = services.GetRequiredService>(); - var context = services.GetService(); + using var scope = services.CreateScope(); + var scopeServices = scope.ServiceProvider; + var logger = scopeServices.GetRequiredService>(); + var context = scopeServices.GetService(); try { @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Hosting if (underK8s) { - InvokeSeeder(seeder, context, services); + InvokeSeeder(seeder, context, scopeServices); } else { @@ -43,14 +43,14 @@ namespace Microsoft.AspNetCore.Hosting sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries); + logger.LogWarning(exception, "[{prefix}] Error migrating database (attempt {retry} of {retries})", nameof(TContext), retry, retries); }); //if the sql server container is not created on run docker compose this //migration can't fail for network related exception. The retry options for DbContext only //apply to transient exceptions // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) - retry.Execute(() => InvokeSeeder(seeder, context, services)); + retry.Execute(() => InvokeSeeder(seeder, context, scopeServices)); } logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Hosting } } - return webHost; + return services; } private static void InvokeSeeder(Action seeder, TContext context, IServiceProvider services) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props new file mode 100644 index 000000000..9a7ce49ec --- /dev/null +++ b/src/Directory.Packages.props @@ -0,0 +1,93 @@ + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NuGet.config b/src/NuGet.config index f578cf969..61b13d16b 100644 --- a/src/NuGet.config +++ b/src/NuGet.config @@ -1,9 +1,10 @@  - - - - + + - \ No newline at end of file + + + + diff --git a/src/Services/Basket/Basket.API/Auth/Client/enable-token-client.js b/src/Services/Basket/Basket.API/Auth/Client/enable-token-client.js deleted file mode 100644 index 3c207e7ea..000000000 --- a/src/Services/Basket/Basket.API/Auth/Client/enable-token-client.js +++ /dev/null @@ -1,28 +0,0 @@ -(function ($, swaggerUi) { - $(function () { - var settings = { - authority: 'https://localhost:5105', - client_id: 'js', - popup_redirect_uri: window.location.protocol - + '//' - + window.location.host - + '/tokenclient/popup.html', - - response_type: 'id_token token', - scope: 'openid profile basket', - - filter_protocol_claims: true - }, - manager = new OidcTokenManager(settings), - $inputApiKey = $('#input_apiKey'); - - $inputApiKey.on('dblclick', function () { - manager.openPopupForTokenAsync() - .then(function () { - $inputApiKey.val(manager.access_token).change(); - }, function (error) { - console.error(error); - }); - }); - }); -})(jQuery, window.swaggerUi); \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Auth/Client/oidc-token-manager.js b/src/Services/Basket/Basket.API/Auth/Client/oidc-token-manager.js deleted file mode 100644 index a6f3f29e5..000000000 --- a/src/Services/Basket/Basket.API/Auth/Client/oidc-token-manager.js +++ /dev/null @@ -1,8896 +0,0 @@ -(function () { - - // globals - var _promiseFactory; - var _httpRequest; -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -/** - * CryptoJS core components. - */ -var CryptoJS = CryptoJS || (function (Math, undefined) { - /** - * CryptoJS namespace. - */ - var C = {}; - - /** - * Library namespace. - */ - var C_lib = C.lib = {}; - - /** - * Base object for prototypal inheritance. - */ - var Base = C_lib.Base = (function () { - function F() {} - - return { - /** - * Creates a new object that inherits from this object. - * - * @param {Object} overrides Properties to copy into the new object. - * - * @return {Object} The new object. - * - * @static - * - * @example - * - * var MyType = CryptoJS.lib.Base.extend({ - * field: 'value', - * - * method: function () { - * } - * }); - */ - extend: function (overrides) { - // Spawn - F.prototype = this; - var subtype = new F(); - - // Augment - if (overrides) { - subtype.mixIn(overrides); - } - - // Create default initializer - if (!subtype.hasOwnProperty('init')) { - subtype.init = function () { - subtype.$super.init.apply(this, arguments); - }; - } - - // Initializer's prototype is the subtype object - subtype.init.prototype = subtype; - - // Reference supertype - subtype.$super = this; - - return subtype; - }, - - /** - * Extends this object and runs the init method. - * Arguments to create() will be passed to init(). - * - * @return {Object} The new object. - * - * @static - * - * @example - * - * var instance = MyType.create(); - */ - create: function () { - var instance = this.extend(); - instance.init.apply(instance, arguments); - - return instance; - }, - - /** - * Initializes a newly created object. - * Override this method to add some logic when your objects are created. - * - * @example - * - * var MyType = CryptoJS.lib.Base.extend({ - * init: function () { - * // ... - * } - * }); - */ - init: function () { - }, - - /** - * Copies properties into this object. - * - * @param {Object} properties The properties to mix in. - * - * @example - * - * MyType.mixIn({ - * field: 'value' - * }); - */ - mixIn: function (properties) { - for (var propertyName in properties) { - if (properties.hasOwnProperty(propertyName)) { - this[propertyName] = properties[propertyName]; - } - } - - // IE won't copy toString using the loop above - if (properties.hasOwnProperty('toString')) { - this.toString = properties.toString; - } - }, - - /** - * Creates a copy of this object. - * - * @return {Object} The clone. - * - * @example - * - * var clone = instance.clone(); - */ - clone: function () { - return this.init.prototype.extend(this); - } - }; - }()); - - /** - * An array of 32-bit words. - * - * @property {Array} words The array of 32-bit words. - * @property {number} sigBytes The number of significant bytes in this word array. - */ - var WordArray = C_lib.WordArray = Base.extend({ - /** - * Initializes a newly created word array. - * - * @param {Array} words (Optional) An array of 32-bit words. - * @param {number} sigBytes (Optional) The number of significant bytes in the words. - * - * @example - * - * var wordArray = CryptoJS.lib.WordArray.create(); - * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); - * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); - */ - init: function (words, sigBytes) { - words = this.words = words || []; - - if (sigBytes != undefined) { - this.sigBytes = sigBytes; - } else { - this.sigBytes = words.length * 4; - } - }, - - /** - * Converts this word array to a string. - * - * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex - * - * @return {string} The stringified word array. - * - * @example - * - * var string = wordArray + ''; - * var string = wordArray.toString(); - * var string = wordArray.toString(CryptoJS.enc.Utf8); - */ - toString: function (encoder) { - return (encoder || Hex).stringify(this); - }, - - /** - * Concatenates a word array to this word array. - * - * @param {WordArray} wordArray The word array to append. - * - * @return {WordArray} This word array. - * - * @example - * - * wordArray1.concat(wordArray2); - */ - concat: function (wordArray) { - // Shortcuts - var thisWords = this.words; - var thatWords = wordArray.words; - var thisSigBytes = this.sigBytes; - var thatSigBytes = wordArray.sigBytes; - - // Clamp excess bits - this.clamp(); - - // Concat - if (thisSigBytes % 4) { - // Copy one byte at a time - for (var i = 0; i < thatSigBytes; i++) { - var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); - } - } else if (thatWords.length > 0xffff) { - // Copy one word at a time - for (var i = 0; i < thatSigBytes; i += 4) { - thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; - } - } else { - // Copy all words at once - thisWords.push.apply(thisWords, thatWords); - } - this.sigBytes += thatSigBytes; - - // Chainable - return this; - }, - - /** - * Removes insignificant bits. - * - * @example - * - * wordArray.clamp(); - */ - clamp: function () { - // Shortcuts - var words = this.words; - var sigBytes = this.sigBytes; - - // Clamp - words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); - words.length = Math.ceil(sigBytes / 4); - }, - - /** - * Creates a copy of this word array. - * - * @return {WordArray} The clone. - * - * @example - * - * var clone = wordArray.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - clone.words = this.words.slice(0); - - return clone; - }, - - /** - * Creates a word array filled with random bytes. - * - * @param {number} nBytes The number of random bytes to generate. - * - * @return {WordArray} The random word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.lib.WordArray.random(16); - */ - random: function (nBytes) { - var words = []; - for (var i = 0; i < nBytes; i += 4) { - words.push((Math.random() * 0x100000000) | 0); - } - - return new WordArray.init(words, nBytes); - } - }); - - /** - * Encoder namespace. - */ - var C_enc = C.enc = {}; - - /** - * Hex encoding strategy. - */ - var Hex = C_enc.Hex = { - /** - * Converts a word array to a hex string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The hex string. - * - * @static - * - * @example - * - * var hexString = CryptoJS.enc.Hex.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var hexChars = []; - for (var i = 0; i < sigBytes; i++) { - var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - hexChars.push((bite >>> 4).toString(16)); - hexChars.push((bite & 0x0f).toString(16)); - } - - return hexChars.join(''); - }, - - /** - * Converts a hex string to a word array. - * - * @param {string} hexStr The hex string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Hex.parse(hexString); - */ - parse: function (hexStr) { - // Shortcut - var hexStrLength = hexStr.length; - - // Convert - var words = []; - for (var i = 0; i < hexStrLength; i += 2) { - words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); - } - - return new WordArray.init(words, hexStrLength / 2); - } - }; - - /** - * Latin1 encoding strategy. - */ - var Latin1 = C_enc.Latin1 = { - /** - * Converts a word array to a Latin1 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The Latin1 string. - * - * @static - * - * @example - * - * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var latin1Chars = []; - for (var i = 0; i < sigBytes; i++) { - var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - latin1Chars.push(String.fromCharCode(bite)); - } - - return latin1Chars.join(''); - }, - - /** - * Converts a Latin1 string to a word array. - * - * @param {string} latin1Str The Latin1 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); - */ - parse: function (latin1Str) { - // Shortcut - var latin1StrLength = latin1Str.length; - - // Convert - var words = []; - for (var i = 0; i < latin1StrLength; i++) { - words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); - } - - return new WordArray.init(words, latin1StrLength); - } - }; - - /** - * UTF-8 encoding strategy. - */ - var Utf8 = C_enc.Utf8 = { - /** - * Converts a word array to a UTF-8 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The UTF-8 string. - * - * @static - * - * @example - * - * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); - */ - stringify: function (wordArray) { - try { - return decodeURIComponent(escape(Latin1.stringify(wordArray))); - } catch (e) { - throw new Error('Malformed UTF-8 data'); - } - }, - - /** - * Converts a UTF-8 string to a word array. - * - * @param {string} utf8Str The UTF-8 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); - */ - parse: function (utf8Str) { - return Latin1.parse(unescape(encodeURIComponent(utf8Str))); - } - }; - - /** - * Abstract buffered block algorithm template. - * - * The property blockSize must be implemented in a concrete subtype. - * - * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 - */ - var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ - /** - * Resets this block algorithm's data buffer to its initial state. - * - * @example - * - * bufferedBlockAlgorithm.reset(); - */ - reset: function () { - // Initial values - this._data = new WordArray.init(); - this._nDataBytes = 0; - }, - - /** - * Adds new data to this block algorithm's buffer. - * - * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. - * - * @example - * - * bufferedBlockAlgorithm._append('data'); - * bufferedBlockAlgorithm._append(wordArray); - */ - _append: function (data) { - // Convert string to WordArray, else assume WordArray already - if (typeof data == 'string') { - data = Utf8.parse(data); - } - - // Append - this._data.concat(data); - this._nDataBytes += data.sigBytes; - }, - - /** - * Processes available data blocks. - * - * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. - * - * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. - * - * @return {WordArray} The processed data. - * - * @example - * - * var processedData = bufferedBlockAlgorithm._process(); - * var processedData = bufferedBlockAlgorithm._process(!!'flush'); - */ - _process: function (doFlush) { - // Shortcuts - var data = this._data; - var dataWords = data.words; - var dataSigBytes = data.sigBytes; - var blockSize = this.blockSize; - var blockSizeBytes = blockSize * 4; - - // Count blocks ready - var nBlocksReady = dataSigBytes / blockSizeBytes; - if (doFlush) { - // Round up to include partial blocks - nBlocksReady = Math.ceil(nBlocksReady); - } else { - // Round down to include only full blocks, - // less the number of blocks that must remain in the buffer - nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); - } - - // Count words ready - var nWordsReady = nBlocksReady * blockSize; - - // Count bytes ready - var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); - - // Process blocks - if (nWordsReady) { - for (var offset = 0; offset < nWordsReady; offset += blockSize) { - // Perform concrete-algorithm logic - this._doProcessBlock(dataWords, offset); - } - - // Remove processed words - var processedWords = dataWords.splice(0, nWordsReady); - data.sigBytes -= nBytesReady; - } - - // Return processed words - return new WordArray.init(processedWords, nBytesReady); - }, - - /** - * Creates a copy of this object. - * - * @return {Object} The clone. - * - * @example - * - * var clone = bufferedBlockAlgorithm.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - clone._data = this._data.clone(); - - return clone; - }, - - _minBufferSize: 0 - }); - - /** - * Abstract hasher template. - * - * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) - */ - var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ - /** - * Configuration options. - */ - cfg: Base.extend(), - - /** - * Initializes a newly created hasher. - * - * @param {Object} cfg (Optional) The configuration options to use for this hash computation. - * - * @example - * - * var hasher = CryptoJS.algo.SHA256.create(); - */ - init: function (cfg) { - // Apply config defaults - this.cfg = this.cfg.extend(cfg); - - // Set initial values - this.reset(); - }, - - /** - * Resets this hasher to its initial state. - * - * @example - * - * hasher.reset(); - */ - reset: function () { - // Reset data buffer - BufferedBlockAlgorithm.reset.call(this); - - // Perform concrete-hasher logic - this._doReset(); - }, - - /** - * Updates this hasher with a message. - * - * @param {WordArray|string} messageUpdate The message to append. - * - * @return {Hasher} This hasher. - * - * @example - * - * hasher.update('message'); - * hasher.update(wordArray); - */ - update: function (messageUpdate) { - // Append - this._append(messageUpdate); - - // Update the hash - this._process(); - - // Chainable - return this; - }, - - /** - * Finalizes the hash computation. - * Note that the finalize operation is effectively a destructive, read-once operation. - * - * @param {WordArray|string} messageUpdate (Optional) A final message update. - * - * @return {WordArray} The hash. - * - * @example - * - * var hash = hasher.finalize(); - * var hash = hasher.finalize('message'); - * var hash = hasher.finalize(wordArray); - */ - finalize: function (messageUpdate) { - // Final message update - if (messageUpdate) { - this._append(messageUpdate); - } - - // Perform concrete-hasher logic - var hash = this._doFinalize(); - - return hash; - }, - - blockSize: 512/32, - - /** - * Creates a shortcut function to a hasher's object interface. - * - * @param {Hasher} hasher The hasher to create a helper for. - * - * @return {Function} The shortcut function. - * - * @static - * - * @example - * - * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); - */ - _createHelper: function (hasher) { - return function (message, cfg) { - return new hasher.init(cfg).finalize(message); - }; - }, - - /** - * Creates a shortcut function to the HMAC's object interface. - * - * @param {Hasher} hasher The hasher to use in this HMAC helper. - * - * @return {Function} The shortcut function. - * - * @static - * - * @example - * - * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); - */ - _createHmacHelper: function (hasher) { - return function (message, key) { - return new C_algo.HMAC.init(hasher, key).finalize(message); - }; - } - }); - - /** - * Algorithm namespace. - */ - var C_algo = C.algo = {}; - - return C; -}(Math)); - -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_algo = C.algo; - - // Reusable object - var W = []; - - /** - * SHA-1 hash algorithm. - */ - var SHA1 = C_algo.SHA1 = Hasher.extend({ - _doReset: function () { - this._hash = new WordArray.init([ - 0x67452301, 0xefcdab89, - 0x98badcfe, 0x10325476, - 0xc3d2e1f0 - ]); - }, - - _doProcessBlock: function (M, offset) { - // Shortcut - var H = this._hash.words; - - // Working variables - var a = H[0]; - var b = H[1]; - var c = H[2]; - var d = H[3]; - var e = H[4]; - - // Computation - for (var i = 0; i < 80; i++) { - if (i < 16) { - W[i] = M[offset + i] | 0; - } else { - var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; - W[i] = (n << 1) | (n >>> 31); - } - - var t = ((a << 5) | (a >>> 27)) + e + W[i]; - if (i < 20) { - t += ((b & c) | (~b & d)) + 0x5a827999; - } else if (i < 40) { - t += (b ^ c ^ d) + 0x6ed9eba1; - } else if (i < 60) { - t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; - } else /* if (i < 80) */ { - t += (b ^ c ^ d) - 0x359d3e2a; - } - - e = d; - d = c; - c = (b << 30) | (b >>> 2); - b = a; - a = t; - } - - // Intermediate hash value - H[0] = (H[0] + a) | 0; - H[1] = (H[1] + b) | 0; - H[2] = (H[2] + c) | 0; - H[3] = (H[3] + d) | 0; - H[4] = (H[4] + e) | 0; - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Return final computed hash - return this._hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA1('message'); - * var hash = CryptoJS.SHA1(wordArray); - */ - C.SHA1 = Hasher._createHelper(SHA1); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA1(message, key); - */ - C.HmacSHA1 = Hasher._createHmacHelper(SHA1); -}()); - -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function (Math) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_algo = C.algo; - - // Initialization and round constants tables - var H = []; - var K = []; - - // Compute constants - (function () { - function isPrime(n) { - var sqrtN = Math.sqrt(n); - for (var factor = 2; factor <= sqrtN; factor++) { - if (!(n % factor)) { - return false; - } - } - - return true; - } - - function getFractionalBits(n) { - return ((n - (n | 0)) * 0x100000000) | 0; - } - - var n = 2; - var nPrime = 0; - while (nPrime < 64) { - if (isPrime(n)) { - if (nPrime < 8) { - H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); - } - K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); - - nPrime++; - } - - n++; - } - }()); - - // Reusable object - var W = []; - - /** - * SHA-256 hash algorithm. - */ - var SHA256 = C_algo.SHA256 = Hasher.extend({ - _doReset: function () { - this._hash = new WordArray.init(H.slice(0)); - }, - - _doProcessBlock: function (M, offset) { - // Shortcut - var H = this._hash.words; - - // Working variables - var a = H[0]; - var b = H[1]; - var c = H[2]; - var d = H[3]; - var e = H[4]; - var f = H[5]; - var g = H[6]; - var h = H[7]; - - // Computation - for (var i = 0; i < 64; i++) { - if (i < 16) { - W[i] = M[offset + i] | 0; - } else { - var gamma0x = W[i - 15]; - var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ - ((gamma0x << 14) | (gamma0x >>> 18)) ^ - (gamma0x >>> 3); - - var gamma1x = W[i - 2]; - var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ - ((gamma1x << 13) | (gamma1x >>> 19)) ^ - (gamma1x >>> 10); - - W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; - } - - var ch = (e & f) ^ (~e & g); - var maj = (a & b) ^ (a & c) ^ (b & c); - - var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); - var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); - - var t1 = h + sigma1 + ch + K[i] + W[i]; - var t2 = sigma0 + maj; - - h = g; - g = f; - f = e; - e = (d + t1) | 0; - d = c; - c = b; - b = a; - a = (t1 + t2) | 0; - } - - // Intermediate hash value - H[0] = (H[0] + a) | 0; - H[1] = (H[1] + b) | 0; - H[2] = (H[2] + c) | 0; - H[3] = (H[3] + d) | 0; - H[4] = (H[4] + e) | 0; - H[5] = (H[5] + f) | 0; - H[6] = (H[6] + g) | 0; - H[7] = (H[7] + h) | 0; - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Return final computed hash - return this._hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA256('message'); - * var hash = CryptoJS.SHA256(wordArray); - */ - C.SHA256 = Hasher._createHelper(SHA256); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA256(message, key); - */ - C.HmacSHA256 = Hasher._createHmacHelper(SHA256); -}(Math)); - -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function (undefined) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Base = C_lib.Base; - var X32WordArray = C_lib.WordArray; - - /** - * x64 namespace. - */ - var C_x64 = C.x64 = {}; - - /** - * A 64-bit word. - */ - var X64Word = C_x64.Word = Base.extend({ - /** - * Initializes a newly created 64-bit word. - * - * @param {number} high The high 32 bits. - * @param {number} low The low 32 bits. - * - * @example - * - * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); - */ - init: function (high, low) { - this.high = high; - this.low = low; - } - - /** - * Bitwise NOTs this word. - * - * @return {X64Word} A new x64-Word object after negating. - * - * @example - * - * var negated = x64Word.not(); - */ - // not: function () { - // var high = ~this.high; - // var low = ~this.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise ANDs this word with the passed word. - * - * @param {X64Word} word The x64-Word to AND with this word. - * - * @return {X64Word} A new x64-Word object after ANDing. - * - * @example - * - * var anded = x64Word.and(anotherX64Word); - */ - // and: function (word) { - // var high = this.high & word.high; - // var low = this.low & word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise ORs this word with the passed word. - * - * @param {X64Word} word The x64-Word to OR with this word. - * - * @return {X64Word} A new x64-Word object after ORing. - * - * @example - * - * var ored = x64Word.or(anotherX64Word); - */ - // or: function (word) { - // var high = this.high | word.high; - // var low = this.low | word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise XORs this word with the passed word. - * - * @param {X64Word} word The x64-Word to XOR with this word. - * - * @return {X64Word} A new x64-Word object after XORing. - * - * @example - * - * var xored = x64Word.xor(anotherX64Word); - */ - // xor: function (word) { - // var high = this.high ^ word.high; - // var low = this.low ^ word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Shifts this word n bits to the left. - * - * @param {number} n The number of bits to shift. - * - * @return {X64Word} A new x64-Word object after shifting. - * - * @example - * - * var shifted = x64Word.shiftL(25); - */ - // shiftL: function (n) { - // if (n < 32) { - // var high = (this.high << n) | (this.low >>> (32 - n)); - // var low = this.low << n; - // } else { - // var high = this.low << (n - 32); - // var low = 0; - // } - - // return X64Word.create(high, low); - // }, - - /** - * Shifts this word n bits to the right. - * - * @param {number} n The number of bits to shift. - * - * @return {X64Word} A new x64-Word object after shifting. - * - * @example - * - * var shifted = x64Word.shiftR(7); - */ - // shiftR: function (n) { - // if (n < 32) { - // var low = (this.low >>> n) | (this.high << (32 - n)); - // var high = this.high >>> n; - // } else { - // var low = this.high >>> (n - 32); - // var high = 0; - // } - - // return X64Word.create(high, low); - // }, - - /** - * Rotates this word n bits to the left. - * - * @param {number} n The number of bits to rotate. - * - * @return {X64Word} A new x64-Word object after rotating. - * - * @example - * - * var rotated = x64Word.rotL(25); - */ - // rotL: function (n) { - // return this.shiftL(n).or(this.shiftR(64 - n)); - // }, - - /** - * Rotates this word n bits to the right. - * - * @param {number} n The number of bits to rotate. - * - * @return {X64Word} A new x64-Word object after rotating. - * - * @example - * - * var rotated = x64Word.rotR(7); - */ - // rotR: function (n) { - // return this.shiftR(n).or(this.shiftL(64 - n)); - // }, - - /** - * Adds this word with the passed word. - * - * @param {X64Word} word The x64-Word to add with this word. - * - * @return {X64Word} A new x64-Word object after adding. - * - * @example - * - * var added = x64Word.add(anotherX64Word); - */ - // add: function (word) { - // var low = (this.low + word.low) | 0; - // var carry = (low >>> 0) < (this.low >>> 0) ? 1 : 0; - // var high = (this.high + word.high + carry) | 0; - - // return X64Word.create(high, low); - // } - }); - - /** - * An array of 64-bit words. - * - * @property {Array} words The array of CryptoJS.x64.Word objects. - * @property {number} sigBytes The number of significant bytes in this word array. - */ - var X64WordArray = C_x64.WordArray = Base.extend({ - /** - * Initializes a newly created word array. - * - * @param {Array} words (Optional) An array of CryptoJS.x64.Word objects. - * @param {number} sigBytes (Optional) The number of significant bytes in the words. - * - * @example - * - * var wordArray = CryptoJS.x64.WordArray.create(); - * - * var wordArray = CryptoJS.x64.WordArray.create([ - * CryptoJS.x64.Word.create(0x00010203, 0x04050607), - * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) - * ]); - * - * var wordArray = CryptoJS.x64.WordArray.create([ - * CryptoJS.x64.Word.create(0x00010203, 0x04050607), - * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) - * ], 10); - */ - init: function (words, sigBytes) { - words = this.words = words || []; - - if (sigBytes != undefined) { - this.sigBytes = sigBytes; - } else { - this.sigBytes = words.length * 8; - } - }, - - /** - * Converts this 64-bit word array to a 32-bit word array. - * - * @return {CryptoJS.lib.WordArray} This word array's data as a 32-bit word array. - * - * @example - * - * var x32WordArray = x64WordArray.toX32(); - */ - toX32: function () { - // Shortcuts - var x64Words = this.words; - var x64WordsLength = x64Words.length; - - // Convert - var x32Words = []; - for (var i = 0; i < x64WordsLength; i++) { - var x64Word = x64Words[i]; - x32Words.push(x64Word.high); - x32Words.push(x64Word.low); - } - - return X32WordArray.create(x32Words, this.sigBytes); - }, - - /** - * Creates a copy of this word array. - * - * @return {X64WordArray} The clone. - * - * @example - * - * var clone = x64WordArray.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - - // Clone "words" array - var words = clone.words = this.words.slice(0); - - // Clone each X64Word object - var wordsLength = words.length; - for (var i = 0; i < wordsLength; i++) { - words[i] = words[i].clone(); - } - - return clone; - } - }); -}()); -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Hasher = C_lib.Hasher; - var C_x64 = C.x64; - var X64Word = C_x64.Word; - var X64WordArray = C_x64.WordArray; - var C_algo = C.algo; - - function X64Word_create() { - return X64Word.create.apply(X64Word, arguments); - } - - // Constants - var K = [ - X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), - X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), - X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), - X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), - X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), - X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), - X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), - X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), - X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), - X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), - X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), - X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), - X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), - X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), - X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), - X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), - X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), - X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), - X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), - X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), - X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), - X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), - X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), - X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), - X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), - X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), - X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), - X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), - X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), - X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), - X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), - X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), - X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), - X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), - X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), - X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), - X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), - X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), - X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), - X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) - ]; - - // Reusable objects - var W = []; - (function () { - for (var i = 0; i < 80; i++) { - W[i] = X64Word_create(); - } - }()); - - /** - * SHA-512 hash algorithm. - */ - var SHA512 = C_algo.SHA512 = Hasher.extend({ - _doReset: function () { - this._hash = new X64WordArray.init([ - new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), - new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), - new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), - new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) - ]); - }, - - _doProcessBlock: function (M, offset) { - // Shortcuts - var H = this._hash.words; - - var H0 = H[0]; - var H1 = H[1]; - var H2 = H[2]; - var H3 = H[3]; - var H4 = H[4]; - var H5 = H[5]; - var H6 = H[6]; - var H7 = H[7]; - - var H0h = H0.high; - var H0l = H0.low; - var H1h = H1.high; - var H1l = H1.low; - var H2h = H2.high; - var H2l = H2.low; - var H3h = H3.high; - var H3l = H3.low; - var H4h = H4.high; - var H4l = H4.low; - var H5h = H5.high; - var H5l = H5.low; - var H6h = H6.high; - var H6l = H6.low; - var H7h = H7.high; - var H7l = H7.low; - - // Working variables - var ah = H0h; - var al = H0l; - var bh = H1h; - var bl = H1l; - var ch = H2h; - var cl = H2l; - var dh = H3h; - var dl = H3l; - var eh = H4h; - var el = H4l; - var fh = H5h; - var fl = H5l; - var gh = H6h; - var gl = H6l; - var hh = H7h; - var hl = H7l; - - // Rounds - for (var i = 0; i < 80; i++) { - // Shortcut - var Wi = W[i]; - - // Extend message - if (i < 16) { - var Wih = Wi.high = M[offset + i * 2] | 0; - var Wil = Wi.low = M[offset + i * 2 + 1] | 0; - } else { - // Gamma0 - var gamma0x = W[i - 15]; - var gamma0xh = gamma0x.high; - var gamma0xl = gamma0x.low; - var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); - var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); - - // Gamma1 - var gamma1x = W[i - 2]; - var gamma1xh = gamma1x.high; - var gamma1xl = gamma1x.low; - var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); - var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); - - // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] - var Wi7 = W[i - 7]; - var Wi7h = Wi7.high; - var Wi7l = Wi7.low; - - var Wi16 = W[i - 16]; - var Wi16h = Wi16.high; - var Wi16l = Wi16.low; - - var Wil = gamma0l + Wi7l; - var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); - var Wil = Wil + gamma1l; - var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); - var Wil = Wil + Wi16l; - var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); - - Wi.high = Wih; - Wi.low = Wil; - } - - var chh = (eh & fh) ^ (~eh & gh); - var chl = (el & fl) ^ (~el & gl); - var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); - var majl = (al & bl) ^ (al & cl) ^ (bl & cl); - - var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); - var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); - var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); - var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); - - // t1 = h + sigma1 + ch + K[i] + W[i] - var Ki = K[i]; - var Kih = Ki.high; - var Kil = Ki.low; - - var t1l = hl + sigma1l; - var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); - var t1l = t1l + chl; - var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); - var t1l = t1l + Kil; - var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); - var t1l = t1l + Wil; - var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); - - // t2 = sigma0 + maj - var t2l = sigma0l + majl; - var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); - - // Update working variables - hh = gh; - hl = gl; - gh = fh; - gl = fl; - fh = eh; - fl = el; - el = (dl + t1l) | 0; - eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; - dh = ch; - dl = cl; - ch = bh; - cl = bl; - bh = ah; - bl = al; - al = (t1l + t2l) | 0; - ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; - } - - // Intermediate hash value - H0l = H0.low = (H0l + al); - H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); - H1l = H1.low = (H1l + bl); - H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); - H2l = H2.low = (H2l + cl); - H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); - H3l = H3.low = (H3l + dl); - H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); - H4l = H4.low = (H4l + el); - H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); - H5l = H5.low = (H5l + fl); - H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); - H6l = H6.low = (H6l + gl); - H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); - H7l = H7.low = (H7l + hl); - H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Convert hash to 32-bit word array before returning - var hash = this._hash.toX32(); - - // Return final computed hash - return hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - }, - - blockSize: 1024/32 - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA512('message'); - * var hash = CryptoJS.SHA512(wordArray); - */ - C.SHA512 = Hasher._createHelper(SHA512); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA512(message, key); - */ - C.HmacSHA512 = Hasher._createHmacHelper(SHA512); -}()); - - -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -var b64pad="="; - -function hex2b64(h) { - var i; - var c; - var ret = ""; - for(i = 0; i+3 <= h.length; i+=3) { - c = parseInt(h.substring(i,i+3),16); - ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); - } - if(i+1 == h.length) { - c = parseInt(h.substring(i,i+1),16); - ret += b64map.charAt(c << 2); - } - else if(i+2 == h.length) { - c = parseInt(h.substring(i,i+2),16); - ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); - } - if (b64pad) while((ret.length & 3) > 0) ret += b64pad; - return ret; -} - -// convert a base64 string to hex -function b64tohex(s) { - var ret = "" - var i; - var k = 0; // b64 state, 0-3 - var slop; - var v; - for(i = 0; i < s.length; ++i) { - if(s.charAt(i) == b64pad) break; - v = b64map.indexOf(s.charAt(i)); - if(v < 0) continue; - if(k == 0) { - ret += int2char(v >> 2); - slop = v & 3; - k = 1; - } - else if(k == 1) { - ret += int2char((slop << 2) | (v >> 4)); - slop = v & 0xf; - k = 2; - } - else if(k == 2) { - ret += int2char(slop); - ret += int2char(v >> 2); - slop = v & 3; - k = 3; - } - else { - ret += int2char((slop << 2) | (v >> 4)); - ret += int2char(v & 0xf); - k = 0; - } - } - if(k == 1) - ret += int2char(slop << 2); - return ret; -} - -// convert a base64 string to a byte/number array -function b64toBA(s) { - //piggyback on b64tohex for now, optimize later - var h = b64tohex(s); - var i; - var a = new Array(); - for(i = 0; 2*i < h.length; ++i) { - a[i] = parseInt(h.substring(2*i,2*i+2),16); - } - return a; -} -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if(a != null) - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+this.DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); - } - else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return (this.s<0)?-r:r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; - } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; - } - if(a.t < this.t) { - c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = this.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -function Classic(m) { this.m = m; } -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -function Montgomery(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp&0x7fff; - this.mph = this.mp>>15; - this.um = (1<<(m.DB-15))-1; - this.mt2 = 2*m.t; -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; - -// public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -// Copyright (c) 2005-2009 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Extended JavaScript BN functions, required for RSA private ops. - -// Version 1.1: new BigInteger("0", 10) returns "proper" zero -// Version 1.2: square() API, isProbablePrime fix - -// (public) -function bnClone() { var r = nbi(); this.copyTo(r); return r; } - -// (public) return value as integer -function bnIntValue() { - if(this.s < 0) { - if(this.t == 1) return this[0]-this.DV; - else if(this.t == 0) return -1; - } - else if(this.t == 1) return this[0]; - else if(this.t == 0) return 0; - // assumes 16 < DB < 32 - return ((this[1]&((1<<(32-this.DB))-1))<>24; } - -// (public) return value as short (assumes DB>=16) -function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } - -// (protected) return x s.t. r^x < DV -function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } - -// (public) 0 if this == 0, 1 if this > 0 -function bnSigNum() { - if(this.s < 0) return -1; - else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; - else return 1; -} - -// (protected) convert to radix string -function bnpToRadix(b) { - if(b == null) b = 10; - if(this.signum() == 0 || b < 2 || b > 36) return "0"; - var cs = this.chunkSize(b); - var a = Math.pow(b,cs); - var d = nbv(a), y = nbi(), z = nbi(), r = ""; - this.divRemTo(d,y,z); - while(y.signum() > 0) { - r = (a+z.intValue()).toString(b).substr(1) + r; - y.divRemTo(d,y,z); - } - return z.intValue().toString(b) + r; -} - -// (protected) convert from radix string -function bnpFromRadix(s,b) { - this.fromInt(0); - if(b == null) b = 10; - var cs = this.chunkSize(b); - var d = Math.pow(b,cs), mi = false, j = 0, w = 0; - for(var i = 0; i < s.length; ++i) { - var x = intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-" && this.signum() == 0) mi = true; - continue; - } - w = b*w+x; - if(++j >= cs) { - this.dMultiply(d); - this.dAddOffset(w,0); - j = 0; - w = 0; - } - } - if(j > 0) { - this.dMultiply(Math.pow(b,j)); - this.dAddOffset(w,0); - } - if(mi) BigInteger.ZERO.subTo(this,this); -} - -// (protected) alternate constructor -function bnpFromNumber(a,b,c) { - if("number" == typeof b) { - // new BigInteger(int,int,RNG) - if(a < 2) this.fromInt(1); - else { - this.fromNumber(a,c); - if(!this.testBit(a-1)) // force MSB set - this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); - if(this.isEven()) this.dAddOffset(1,0); // force odd - while(!this.isProbablePrime(b)) { - this.dAddOffset(2,0); - if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); - } - } - } - else { - // new BigInteger(int,RNG) - var x = new Array(), t = a&7; - x.length = (a>>3)+1; - b.nextBytes(x); - if(t > 0) x[0] &= ((1< 0) { - if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p) - r[k++] = d|(this.s<<(this.DB-p)); - while(i >= 0) { - if(p < 8) { - d = (this[i]&((1<>(p+=this.DB-8); - } - else { - d = (this[i]>>(p-=8))&0xff; - if(p <= 0) { p += this.DB; --i; } - } - if((d&0x80) != 0) d |= -256; - if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; - if(k > 0 || d != this.s) r[k++] = d; - } - } - return r; -} - -function bnEquals(a) { return(this.compareTo(a)==0); } -function bnMin(a) { return(this.compareTo(a)<0)?this:a; } -function bnMax(a) { return(this.compareTo(a)>0)?this:a; } - -// (protected) r = this op a (bitwise) -function bnpBitwiseTo(a,op,r) { - var i, f, m = Math.min(a.t,this.t); - for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]); - if(a.t < this.t) { - f = a.s&this.DM; - for(i = m; i < this.t; ++i) r[i] = op(this[i],f); - r.t = this.t; - } - else { - f = this.s&this.DM; - for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); - r.t = a.t; - } - r.s = op(this.s,a.s); - r.clamp(); -} - -// (public) this & a -function op_and(x,y) { return x&y; } -function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } - -// (public) this | a -function op_or(x,y) { return x|y; } -function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } - -// (public) this ^ a -function op_xor(x,y) { return x^y; } -function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } - -// (public) this & ~a -function op_andnot(x,y) { return x&~y; } -function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } - -// (public) ~this -function bnNot() { - var r = nbi(); - for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; - r.t = this.t; - r.s = ~this.s; - return r; -} - -// (public) this << n -function bnShiftLeft(n) { - var r = nbi(); - if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); - return r; -} - -// (public) this >> n -function bnShiftRight(n) { - var r = nbi(); - if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); - return r; -} - -// return index of lowest 1-bit in x, x < 2^31 -function lbit(x) { - if(x == 0) return -1; - var r = 0; - if((x&0xffff) == 0) { x >>= 16; r += 16; } - if((x&0xff) == 0) { x >>= 8; r += 8; } - if((x&0xf) == 0) { x >>= 4; r += 4; } - if((x&3) == 0) { x >>= 2; r += 2; } - if((x&1) == 0) ++r; - return r; -} - -// (public) returns index of lowest 1-bit (or -1 if none) -function bnGetLowestSetBit() { - for(var i = 0; i < this.t; ++i) - if(this[i] != 0) return i*this.DB+lbit(this[i]); - if(this.s < 0) return this.t*this.DB; - return -1; -} - -// return number of 1 bits in x -function cbit(x) { - var r = 0; - while(x != 0) { x &= x-1; ++r; } - return r; -} - -// (public) return number of set bits -function bnBitCount() { - var r = 0, x = this.s&this.DM; - for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); - return r; -} - -// (public) true iff nth bit is set -function bnTestBit(n) { - var j = Math.floor(n/this.DB); - if(j >= this.t) return(this.s!=0); - return((this[j]&(1<<(n%this.DB)))!=0); -} - -// (protected) this op (1<>= this.DB; - } - if(a.t < this.t) { - c += a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c += a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += a.s; - } - r.s = (c<0)?-1:0; - if(c > 0) r[i++] = c; - else if(c < -1) r[i++] = this.DV+c; - r.t = i; - r.clamp(); -} - -// (public) this + a -function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } - -// (public) this - a -function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } - -// (public) this * a -function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } - -// (public) this^2 -function bnSquare() { var r = nbi(); this.squareTo(r); return r; } - -// (public) this / a -function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } - -// (public) this % a -function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } - -// (public) [this/a,this%a] -function bnDivideAndRemainder(a) { - var q = nbi(), r = nbi(); - this.divRemTo(a,q,r); - return new Array(q,r); -} - -// (protected) this *= n, this >= 0, 1 < n < DV -function bnpDMultiply(n) { - this[this.t] = this.am(0,n-1,this,0,0,this.t); - ++this.t; - this.clamp(); -} - -// (protected) this += n << w words, this >= 0 -function bnpDAddOffset(n,w) { - if(n == 0) return; - while(this.t <= w) this[this.t++] = 0; - this[w] += n; - while(this[w] >= this.DV) { - this[w] -= this.DV; - if(++w >= this.t) this[this.t++] = 0; - ++this[w]; - } -} - -// A "null" reducer -function NullExp() {} -function nNop(x) { return x; } -function nMulTo(x,y,r) { x.multiplyTo(y,r); } -function nSqrTo(x,r) { x.squareTo(r); } - -NullExp.prototype.convert = nNop; -NullExp.prototype.revert = nNop; -NullExp.prototype.mulTo = nMulTo; -NullExp.prototype.sqrTo = nSqrTo; - -// (public) this^e -function bnPow(e) { return this.exp(e,new NullExp()); } - -// (protected) r = lower n words of "this * a", a.t <= n -// "this" should be the larger one if appropriate. -function bnpMultiplyLowerTo(a,n,r) { - var i = Math.min(this.t+a.t,n); - r.s = 0; // assumes a,this >= 0 - r.t = i; - while(i > 0) r[--i] = 0; - var j; - for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); - for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); - r.clamp(); -} - -// (protected) r = "this * a" without lower n words, n > 0 -// "this" should be the larger one if appropriate. -function bnpMultiplyUpperTo(a,n,r) { - --n; - var i = r.t = this.t+a.t-n; - r.s = 0; // assumes a,this >= 0 - while(--i >= 0) r[i] = 0; - for(i = Math.max(n-this.t,0); i < a.t; ++i) - r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); - r.clamp(); - r.drShiftTo(1,r); -} - -// Barrett modular reduction -function Barrett(m) { - // setup Barrett - this.r2 = nbi(); - this.q3 = nbi(); - BigInteger.ONE.dlShiftTo(2*m.t,this.r2); - this.mu = this.r2.divide(m); - this.m = m; -} - -function barrettConvert(x) { - if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); - else if(x.compareTo(this.m) < 0) return x; - else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } -} - -function barrettRevert(x) { return x; } - -// x = x mod m (HAC 14.42) -function barrettReduce(x) { - x.drShiftTo(this.m.t-1,this.r2); - if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } - this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); - this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); - while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); - x.subTo(this.r2,x); - while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = x^2 mod m; x != r -function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = x*y mod m; x,y != r -function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Barrett.prototype.convert = barrettConvert; -Barrett.prototype.revert = barrettRevert; -Barrett.prototype.reduce = barrettReduce; -Barrett.prototype.mulTo = barrettMulTo; -Barrett.prototype.sqrTo = barrettSqrTo; - -// (public) this^e % m (HAC 14.85) -function bnModPow(e,m) { - var i = e.bitLength(), k, r = nbv(1), z; - if(i <= 0) return r; - else if(i < 18) k = 1; - else if(i < 48) k = 3; - else if(i < 144) k = 4; - else if(i < 768) k = 5; - else k = 6; - if(i < 8) - z = new Classic(m); - else if(m.isEven()) - z = new Barrett(m); - else - z = new Montgomery(m); - - // precomputation - var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { - var g2 = nbi(); - z.sqrTo(g[1],g2); - while(n <= km) { - g[n] = nbi(); - z.mulTo(g2,g[n-2],g[n]); - n += 2; - } - } - - var j = e.t-1, w, is1 = true, r2 = nbi(), t; - i = nbits(e[j])-1; - while(j >= 0) { - if(i >= k1) w = (e[j]>>(i-k1))&km; - else { - w = (e[j]&((1<<(i+1))-1))<<(k1-i); - if(j > 0) w |= e[j-1]>>(this.DB+i-k1); - } - - n = k; - while((w&1) == 0) { w >>= 1; --n; } - if((i -= n) < 0) { i += this.DB; --j; } - if(is1) { // ret == 1, don't bother squaring or multiplying it - g[w].copyTo(r); - is1 = false; - } - else { - while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } - if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } - z.mulTo(r2,g[w],r); - } - - while(j >= 0 && (e[j]&(1< 0) { - x.rShiftTo(g,x); - y.rShiftTo(g,y); - } - while(x.signum() > 0) { - if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); - if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); - if(x.compareTo(y) >= 0) { - x.subTo(y,x); - x.rShiftTo(1,x); - } - else { - y.subTo(x,y); - y.rShiftTo(1,y); - } - } - if(g > 0) y.lShiftTo(g,y); - return y; -} - -// (protected) this % n, n < 2^26 -function bnpModInt(n) { - if(n <= 0) return 0; - var d = this.DV%n, r = (this.s<0)?n-1:0; - if(this.t > 0) - if(d == 0) r = this[0]%n; - else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; - return r; -} - -// (public) 1/this % m (HAC 14.61) -function bnModInverse(m) { - var ac = m.isEven(); - if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; - var u = m.clone(), v = this.clone(); - var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); - while(u.signum() != 0) { - while(u.isEven()) { - u.rShiftTo(1,u); - if(ac) { - if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } - a.rShiftTo(1,a); - } - else if(!b.isEven()) b.subTo(m,b); - b.rShiftTo(1,b); - } - while(v.isEven()) { - v.rShiftTo(1,v); - if(ac) { - if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } - c.rShiftTo(1,c); - } - else if(!d.isEven()) d.subTo(m,d); - d.rShiftTo(1,d); - } - if(u.compareTo(v) >= 0) { - u.subTo(v,u); - if(ac) a.subTo(c,a); - b.subTo(d,b); - } - else { - v.subTo(u,v); - if(ac) c.subTo(a,c); - d.subTo(b,d); - } - } - if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; - if(d.compareTo(m) >= 0) return d.subtract(m); - if(d.signum() < 0) d.addTo(m,d); else return d; - if(d.signum() < 0) return d.add(m); else return d; -} - -var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997]; -var lplim = (1<<26)/lowprimes[lowprimes.length-1]; - -// (public) test primality with certainty >= 1-.5^t -function bnIsProbablePrime(t) { - var i, x = this.abs(); - if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) { - for(i = 0; i < lowprimes.length; ++i) - if(x[0] == lowprimes[i]) return true; - return false; - } - if(x.isEven()) return false; - i = 1; - while(i < lowprimes.length) { - var m = lowprimes[i], j = i+1; - while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; - m = x.modInt(m); - while(i < j) if(m%lowprimes[i++] == 0) return false; - } - return x.millerRabin(t); -} - -// (protected) true if probably prime (HAC 4.24, Miller-Rabin) -function bnpMillerRabin(t) { - var n1 = this.subtract(BigInteger.ONE); - var k = n1.getLowestSetBit(); - if(k <= 0) return false; - var r = n1.shiftRight(k); - t = (t+1)>>1; - if(t > lowprimes.length) t = lowprimes.length; - var a = nbi(); - for(var i = 0; i < t; ++i) { - //Pick bases at random, instead of starting at 2 - a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]); - var y = a.modPow(r,this); - if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { - var j = 1; - while(j++ < k && y.compareTo(n1) != 0) { - y = y.modPowInt(2,this); - if(y.compareTo(BigInteger.ONE) == 0) return false; - } - if(y.compareTo(n1) != 0) return false; - } - } - return true; -} - -// protected -BigInteger.prototype.chunkSize = bnpChunkSize; -BigInteger.prototype.toRadix = bnpToRadix; -BigInteger.prototype.fromRadix = bnpFromRadix; -BigInteger.prototype.fromNumber = bnpFromNumber; -BigInteger.prototype.bitwiseTo = bnpBitwiseTo; -BigInteger.prototype.changeBit = bnpChangeBit; -BigInteger.prototype.addTo = bnpAddTo; -BigInteger.prototype.dMultiply = bnpDMultiply; -BigInteger.prototype.dAddOffset = bnpDAddOffset; -BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; -BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; -BigInteger.prototype.modInt = bnpModInt; -BigInteger.prototype.millerRabin = bnpMillerRabin; - -// public -BigInteger.prototype.clone = bnClone; -BigInteger.prototype.intValue = bnIntValue; -BigInteger.prototype.byteValue = bnByteValue; -BigInteger.prototype.shortValue = bnShortValue; -BigInteger.prototype.signum = bnSigNum; -BigInteger.prototype.toByteArray = bnToByteArray; -BigInteger.prototype.equals = bnEquals; -BigInteger.prototype.min = bnMin; -BigInteger.prototype.max = bnMax; -BigInteger.prototype.and = bnAnd; -BigInteger.prototype.or = bnOr; -BigInteger.prototype.xor = bnXor; -BigInteger.prototype.andNot = bnAndNot; -BigInteger.prototype.not = bnNot; -BigInteger.prototype.shiftLeft = bnShiftLeft; -BigInteger.prototype.shiftRight = bnShiftRight; -BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; -BigInteger.prototype.bitCount = bnBitCount; -BigInteger.prototype.testBit = bnTestBit; -BigInteger.prototype.setBit = bnSetBit; -BigInteger.prototype.clearBit = bnClearBit; -BigInteger.prototype.flipBit = bnFlipBit; -BigInteger.prototype.add = bnAdd; -BigInteger.prototype.subtract = bnSubtract; -BigInteger.prototype.multiply = bnMultiply; -BigInteger.prototype.divide = bnDivide; -BigInteger.prototype.remainder = bnRemainder; -BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; -BigInteger.prototype.modPow = bnModPow; -BigInteger.prototype.modInverse = bnModInverse; -BigInteger.prototype.pow = bnPow; -BigInteger.prototype.gcd = bnGCD; -BigInteger.prototype.isProbablePrime = bnIsProbablePrime; - -// JSBN-specific extension -BigInteger.prototype.square = bnSquare; - -// BigInteger interfaces not implemented in jsbn: - -// BigInteger(int signum, byte[] magnitude) -// double doubleValue() -// float floatValue() -// int hashCode() -// long longValue() -// static BigInteger valueOf(long val) -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -// Depends on jsbn.js and rng.js - -// Version 1.1: support utf-8 encoding in pkcs1pad2 - -// convert a (hex) string to a bignum object -function parseBigInt(str,r) { - return new BigInteger(str,r); -} - -function linebrk(s,n) { - var ret = ""; - var i = 0; - while(i + n < s.length) { - ret += s.substring(i,i+n) + "\n"; - i += n; - } - return ret + s.substring(i,s.length); -} - -function byte2Hex(b) { - if(b < 0x10) - return "0" + b.toString(16); - else - return b.toString(16); -} - -// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint -function pkcs1pad2(s,n) { - if(n < s.length + 11) { // TODO: fix for utf-8 - alert("Message too long for RSA"); - return null; - } - var ba = new Array(); - var i = s.length - 1; - while(i >= 0 && n > 0) { - var c = s.charCodeAt(i--); - if(c < 128) { // encode using utf-8 - ba[--n] = c; - } - else if((c > 127) && (c < 2048)) { - ba[--n] = (c & 63) | 128; - ba[--n] = (c >> 6) | 192; - } - else { - ba[--n] = (c & 63) | 128; - ba[--n] = ((c >> 6) & 63) | 128; - ba[--n] = (c >> 12) | 224; - } - } - ba[--n] = 0; - var rng = new SecureRandom(); - var x = new Array(); - while(n > 2) { // random non-zero pad - x[0] = 0; - while(x[0] == 0) rng.nextBytes(x); - ba[--n] = x[0]; - } - ba[--n] = 2; - ba[--n] = 0; - return new BigInteger(ba); -} - -// PKCS#1 (OAEP) mask generation function -function oaep_mgf1_arr(seed, len, hash) -{ - var mask = '', i = 0; - - while (mask.length < len) - { - mask += hash(String.fromCharCode.apply(String, seed.concat([ - (i & 0xff000000) >> 24, - (i & 0x00ff0000) >> 16, - (i & 0x0000ff00) >> 8, - i & 0x000000ff]))); - i += 1; - } - - return mask; -} - -var SHA1_SIZE = 20; - -// PKCS#1 (OAEP) pad input string s to n bytes, and return a bigint -function oaep_pad(s, n, hash) -{ - if (s.length + 2 * SHA1_SIZE + 2 > n) - { - throw "Message too long for RSA"; - } - - var PS = '', i; - - for (i = 0; i < n - s.length - 2 * SHA1_SIZE - 2; i += 1) - { - PS += '\x00'; - } - - var DB = rstr_sha1('') + PS + '\x01' + s; - var seed = new Array(SHA1_SIZE); - new SecureRandom().nextBytes(seed); - - var dbMask = oaep_mgf1_arr(seed, DB.length, hash || rstr_sha1); - var maskedDB = []; - - for (i = 0; i < DB.length; i += 1) - { - maskedDB[i] = DB.charCodeAt(i) ^ dbMask.charCodeAt(i); - } - - var seedMask = oaep_mgf1_arr(maskedDB, seed.length, rstr_sha1); - var maskedSeed = [0]; - - for (i = 0; i < seed.length; i += 1) - { - maskedSeed[i + 1] = seed[i] ^ seedMask.charCodeAt(i); - } - - return new BigInteger(maskedSeed.concat(maskedDB)); -} - -// "empty" RSA key constructor -function RSAKey() { - this.n = null; - this.e = 0; - this.d = null; - this.p = null; - this.q = null; - this.dmp1 = null; - this.dmq1 = null; - this.coeff = null; -} - -// Set the public key fields N and e from hex strings -function RSASetPublic(N,E) { - this.isPublic = true; - if (typeof N !== "string") - { - this.n = N; - this.e = E; - } - else if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - } - else - alert("Invalid RSA public key"); -} - -// Perform raw public operation on "x": return x^e (mod n) -function RSADoPublic(x) { - return x.modPowInt(this.e, this.n); -} - -// Return the PKCS#1 RSA encryption of "text" as an even-length hex string -function RSAEncrypt(text) { - var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 OAEP RSA encryption of "text" as an even-length hex string -function RSAEncryptOAEP(text, hash) { - var m = oaep_pad(text, (this.n.bitLength()+7)>>3, hash); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string -//function RSAEncryptB64(text) { -// var h = this.encrypt(text); -// if(h) return hex2b64(h); else return null; -//} - -// protected -RSAKey.prototype.doPublic = RSADoPublic; - -// public -RSAKey.prototype.setPublic = RSASetPublic; -RSAKey.prototype.encrypt = RSAEncrypt; -RSAKey.prototype.encryptOAEP = RSAEncryptOAEP; -//RSAKey.prototype.encrypt_b64 = RSAEncryptB64; - -RSAKey.prototype.type = "RSA"; -/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ -// Depends on rsa.js and jsbn2.js - -// Version 1.1: support utf-8 decoding in pkcs1unpad2 - -// Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext -function pkcs1unpad2(d,n) { - var b = d.toByteArray(); - var i = 0; - while(i < b.length && b[i] == 0) ++i; - if(b.length-i != n-1 || b[i] != 2) - return null; - ++i; - while(b[i] != 0) - if(++i >= b.length) return null; - var ret = ""; - while(++i < b.length) { - var c = b[i] & 255; - if(c < 128) { // utf-8 decode - ret += String.fromCharCode(c); - } - else if((c > 191) && (c < 224)) { - ret += String.fromCharCode(((c & 31) << 6) | (b[i+1] & 63)); - ++i; - } - else { - ret += String.fromCharCode(((c & 15) << 12) | ((b[i+1] & 63) << 6) | (b[i+2] & 63)); - i += 2; - } - } - return ret; -} - -// PKCS#1 (OAEP) mask generation function -function oaep_mgf1_str(seed, len, hash) -{ - var mask = '', i = 0; - - while (mask.length < len) - { - mask += hash(seed + String.fromCharCode.apply(String, [ - (i & 0xff000000) >> 24, - (i & 0x00ff0000) >> 16, - (i & 0x0000ff00) >> 8, - i & 0x000000ff])); - i += 1; - } - - return mask; -} - -var SHA1_SIZE = 20; - -// Undo PKCS#1 (OAEP) padding and, if valid, return the plaintext -function oaep_unpad(d, n, hash) -{ - d = d.toByteArray(); - - var i; - - for (i = 0; i < d.length; i += 1) - { - d[i] &= 0xff; - } - - while (d.length < n) - { - d.unshift(0); - } - - d = String.fromCharCode.apply(String, d); - - if (d.length < 2 * SHA1_SIZE + 2) - { - throw "Cipher too short"; - } - - var maskedSeed = d.substr(1, SHA1_SIZE) - var maskedDB = d.substr(SHA1_SIZE + 1); - - var seedMask = oaep_mgf1_str(maskedDB, SHA1_SIZE, hash || rstr_sha1); - var seed = [], i; - - for (i = 0; i < maskedSeed.length; i += 1) - { - seed[i] = maskedSeed.charCodeAt(i) ^ seedMask.charCodeAt(i); - } - - var dbMask = oaep_mgf1_str(String.fromCharCode.apply(String, seed), - d.length - SHA1_SIZE, rstr_sha1); - - var DB = []; - - for (i = 0; i < maskedDB.length; i += 1) - { - DB[i] = maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i); - } - - DB = String.fromCharCode.apply(String, DB); - - if (DB.substr(0, SHA1_SIZE) !== rstr_sha1('')) - { - throw "Hash mismatch"; - } - - DB = DB.substr(SHA1_SIZE); - - var first_one = DB.indexOf('\x01'); - var last_zero = (first_one != -1) ? DB.substr(0, first_one).lastIndexOf('\x00') : -1; - - if (last_zero + 1 != first_one) - { - throw "Malformed data"; - } - - return DB.substr(first_one + 1); -} - -// Set the private key fields N, e, and d from hex strings -function RSASetPrivate(N,E,D) { - this.isPrivate = true; - if (typeof N !== "string") - { - this.n = N; - this.e = E; - this.d = D; - } - else if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - this.d = parseBigInt(D,16); - } - else - alert("Invalid RSA private key"); -} - -// Set the private key fields N, e, d and CRT params from hex strings -function RSASetPrivateEx(N,E,D,P,Q,DP,DQ,C) { - this.isPrivate = true; - if (N == null) throw "RSASetPrivateEx N == null"; - if (E == null) throw "RSASetPrivateEx E == null"; - if (N.length == 0) throw "RSASetPrivateEx N.length == 0"; - if (E.length == 0) throw "RSASetPrivateEx E.length == 0"; - - if (N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - this.d = parseBigInt(D,16); - this.p = parseBigInt(P,16); - this.q = parseBigInt(Q,16); - this.dmp1 = parseBigInt(DP,16); - this.dmq1 = parseBigInt(DQ,16); - this.coeff = parseBigInt(C,16); - } else { - alert("Invalid RSA private key in RSASetPrivateEx"); - } -} - -// Generate a new random private key B bits long, using public expt E -function RSAGenerate(B,E) { - var rng = new SecureRandom(); - var qs = B>>1; - this.e = parseInt(E,16); - var ee = new BigInteger(E,16); - for(;;) { - for(;;) { - this.p = new BigInteger(B-qs,1,rng); - if(this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break; - } - for(;;) { - this.q = new BigInteger(qs,1,rng); - if(this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break; - } - if(this.p.compareTo(this.q) <= 0) { - var t = this.p; - this.p = this.q; - this.q = t; - } - var p1 = this.p.subtract(BigInteger.ONE); // p1 = p - 1 - var q1 = this.q.subtract(BigInteger.ONE); // q1 = q - 1 - var phi = p1.multiply(q1); - if(phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { - this.n = this.p.multiply(this.q); // this.n = p * q - this.d = ee.modInverse(phi); // this.d = - this.dmp1 = this.d.mod(p1); // this.dmp1 = d mod (p - 1) - this.dmq1 = this.d.mod(q1); // this.dmq1 = d mod (q - 1) - this.coeff = this.q.modInverse(this.p); // this.coeff = (q ^ -1) mod p - break; - } - } - this.isPrivate = true; -} - -// Perform raw private operation on "x": return x^d (mod n) -function RSADoPrivate(x) { - if(this.p == null || this.q == null) - return x.modPow(this.d, this.n); - - // TODO: re-calculate any missing CRT params - var xp = x.mod(this.p).modPow(this.dmp1, this.p); // xp=cp? - var xq = x.mod(this.q).modPow(this.dmq1, this.q); // xq=cq? - - while(xp.compareTo(xq) < 0) - xp = xp.add(this.p); - // NOTE: - // xp.subtract(xq) => cp -cq - // xp.subtract(xq).multiply(this.coeff).mod(this.p) => (cp - cq) * u mod p = h - // xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq) => cq + (h * q) = M - return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); -} - -// Return the PKCS#1 RSA decryption of "ctext". -// "ctext" is an even-length hex string and the output is a plain string. -function RSADecrypt(ctext) { - var c = parseBigInt(ctext, 16); - var m = this.doPrivate(c); - if(m == null) return null; - return pkcs1unpad2(m, (this.n.bitLength()+7)>>3); -} - -// Return the PKCS#1 OAEP RSA decryption of "ctext". -// "ctext" is an even-length hex string and the output is a plain string. -function RSADecryptOAEP(ctext, hash) { - var c = parseBigInt(ctext, 16); - var m = this.doPrivate(c); - if(m == null) return null; - return oaep_unpad(m, (this.n.bitLength()+7)>>3, hash); -} - -// Return the PKCS#1 RSA decryption of "ctext". -// "ctext" is a Base64-encoded string and the output is a plain string. -//function RSAB64Decrypt(ctext) { -// var h = b64tohex(ctext); -// if(h) return this.decrypt(h); else return null; -//} - -// protected -RSAKey.prototype.doPrivate = RSADoPrivate; - -// public -RSAKey.prototype.setPrivate = RSASetPrivate; -RSAKey.prototype.setPrivateEx = RSASetPrivateEx; -RSAKey.prototype.generate = RSAGenerate; -RSAKey.prototype.decrypt = RSADecrypt; -RSAKey.prototype.decryptOAEP = RSADecryptOAEP; -//RSAKey.prototype.b64_decrypt = RSAB64Decrypt; -/*! rsapem-1.1.js (c) 2012 Kenji Urushima | kjur.github.com/jsrsasign/license - */ -// -// rsa-pem.js - adding function for reading/writing PKCS#1 PEM private key -// to RSAKey class. -// -// version: 1.1.1 (2013-Apr-12) -// -// Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com) -// -// This software is licensed under the terms of the MIT License. -// http://kjur.github.com/jsrsasign/license/ -// -// The above copyright and license notice shall be -// included in all copies or substantial portions of the Software. -// -// -// Depends on: -// -// -// -// _RSApem_pemToBase64(sPEM) -// -// removing PEM header, PEM footer and space characters including -// new lines from PEM formatted RSA private key string. -// - -/** - * @fileOverview - * @name rsapem-1.1.js - * @author Kenji Urushima kenji.urushima@gmail.com - * @version 1.1 - * @license MIT License - */ -function _rsapem_pemToBase64(sPEMPrivateKey) { - var s = sPEMPrivateKey; - s = s.replace("-----BEGIN RSA PRIVATE KEY-----", ""); - s = s.replace("-----END RSA PRIVATE KEY-----", ""); - s = s.replace(/[ \n]+/g, ""); - return s; -} - -function _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey) { - var a = new Array(); - var v1 = ASN1HEX.getStartPosOfV_AtObj(hPrivateKey, 0); - var n1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, v1); - var e1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, n1); - var d1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, e1); - var p1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, d1); - var q1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, p1); - var dp1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, q1); - var dq1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, dp1); - var co1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, dq1); - a.push(v1, n1, e1, d1, p1, q1, dp1, dq1, co1); - return a; -} - -function _rsapem_getHexValueArrayOfChildrenFromHex(hPrivateKey) { - var posArray = _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey); - var v = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[0]); - var n = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[1]); - var e = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[2]); - var d = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[3]); - var p = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[4]); - var q = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[5]); - var dp = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[6]); - var dq = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[7]); - var co = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[8]); - var a = new Array(); - a.push(v, n, e, d, p, q, dp, dq, co); - return a; -} - -/** - * read RSA private key from a ASN.1 hexadecimal string - * @name readPrivateKeyFromASN1HexString - * @memberOf RSAKey# - * @function - * @param {String} keyHex ASN.1 hexadecimal string of PKCS#1 private key. - * @since 1.1.1 - */ -function _rsapem_readPrivateKeyFromASN1HexString(keyHex) { - var a = _rsapem_getHexValueArrayOfChildrenFromHex(keyHex); - this.setPrivateEx(a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]); -} - -/** - * read PKCS#1 private key from a string - * @name readPrivateKeyFromPEMString - * @memberOf RSAKey# - * @function - * @param {String} keyPEM string of PKCS#1 private key. - */ -function _rsapem_readPrivateKeyFromPEMString(keyPEM) { - var keyB64 = _rsapem_pemToBase64(keyPEM); - var keyHex = b64tohex(keyB64) // depends base64.js - var a = _rsapem_getHexValueArrayOfChildrenFromHex(keyHex); - this.setPrivateEx(a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]); -} - -RSAKey.prototype.readPrivateKeyFromPEMString = _rsapem_readPrivateKeyFromPEMString; -RSAKey.prototype.readPrivateKeyFromASN1HexString = _rsapem_readPrivateKeyFromASN1HexString; -/*! rsasign-1.2.7.js (c) 2012 Kenji Urushima | kjur.github.com/jsrsasign/license - */ -var _RE_HEXDECONLY=new RegExp("");_RE_HEXDECONLY.compile("[^0-9a-f]","gi");function _rsasign_getHexPaddedDigestInfoForString(d,e,a){var b=function(f){return KJUR.crypto.Util.hashString(f,a)};var c=b(d);return KJUR.crypto.Util.getPaddedDigestInfoHex(c,a,e)}function _zeroPaddingOfSignature(e,d){var c="";var a=d/4-e.length;for(var b=0;b>24,(d&16711680)>>16,(d&65280)>>8,d&255]))));d+=1}return b}function _rsasign_signStringPSS(e,a,d){var c=function(f){return KJUR.crypto.Util.hashHex(f,a)};var b=c(rstrtohex(e));if(d===undefined){d=-1}return this.signWithMessageHashPSS(b,a,d)}function _rsasign_signWithMessageHashPSS(l,a,k){var b=hextorstr(l);var g=b.length;var m=this.n.bitLength()-1;var c=Math.ceil(m/8);var d;var o=function(i){return KJUR.crypto.Util.hashHex(i,a)};if(k===-1||k===undefined){k=g}else{if(k===-2){k=c-g-2}else{if(k<-2){throw"invalid salt length"}}}if(c<(g+k+2)){throw"data too long"}var f="";if(k>0){f=new Array(k);new SecureRandom().nextBytes(f);f=String.fromCharCode.apply(String,f)}var n=hextorstr(o(rstrtohex("\x00\x00\x00\x00\x00\x00\x00\x00"+b+f)));var j=[];for(d=0;d>(8*c-m))&255;q[0]&=~p;for(d=0;dthis.n.bitLength()){return 0}var i=this.doPublic(b);var e=i.toString(16).replace(/^1f+00/,"");var g=_rsasign_getAlgNameAndHashFromHexDisgestInfo(e);if(g.length==0){return false}var d=g[0];var h=g[1];var a=function(k){return KJUR.crypto.Util.hashString(k,d)};var c=a(f);return(h==c)}function _rsasign_verifyWithMessageHash(e,a){a=a.replace(_RE_HEXDECONLY,"");a=a.replace(/[ \n]+/g,"");var b=parseBigInt(a,16);if(b.bitLength()>this.n.bitLength()){return 0}var h=this.doPublic(b);var g=h.toString(16).replace(/^1f+00/,"");var c=_rsasign_getAlgNameAndHashFromHexDisgestInfo(g);if(c.length==0){return false}var d=c[0];var f=c[1];return(f==e)}function _rsasign_verifyStringPSS(c,b,a,f){var e=function(g){return KJUR.crypto.Util.hashHex(g,a)};var d=e(rstrtohex(c));if(f===undefined){f=-1}return this.verifyWithMessageHashPSS(d,b,a,f)}function _rsasign_verifyWithMessageHashPSS(f,s,l,c){var k=new BigInteger(s,16);if(k.bitLength()>this.n.bitLength()){return false}var r=function(i){return KJUR.crypto.Util.hashHex(i,l)};var j=hextorstr(f);var h=j.length;var g=this.n.bitLength()-1;var m=Math.ceil(g/8);var q;if(c===-1||c===undefined){c=h}else{if(c===-2){c=m-h-2}else{if(c<-2){throw"invalid salt length"}}}if(m<(h+c+2)){throw"data too long"}var a=this.doPublic(k).toByteArray();for(q=0;q>(8*m-g))&255;if((d.charCodeAt(0)&p)!==0){throw"bits beyond keysize not zero"}var n=pss_mgf1_str(e,d.length,r);var o=[];for(q=0;qMIT License - */ - -/* - * MEMO: - * f('3082025b02...', 2) ... 82025b ... 3bytes - * f('020100', 2) ... 01 ... 1byte - * f('0203001...', 2) ... 03 ... 1byte - * f('02818003...', 2) ... 8180 ... 2bytes - * f('3080....0000', 2) ... 80 ... -1 - * - * Requirements: - * - ASN.1 type octet length MUST be 1. - * (i.e. ASN.1 primitives like SET, SEQUENCE, INTEGER, OCTETSTRING ...) - */ - -/** - * ASN.1 DER encoded hexadecimal string utility class - * @name ASN1HEX - * @class ASN.1 DER encoded hexadecimal string utility class - * @since jsrsasign 1.1 - */ -var ASN1HEX = new function() { - /** - * get byte length for ASN.1 L(length) bytes - * @name getByteLengthOfL_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return byte length for ASN.1 L(length) bytes - */ - this.getByteLengthOfL_AtObj = function(s, pos) { - if (s.substring(pos + 2, pos + 3) != '8') return 1; - var i = parseInt(s.substring(pos + 3, pos + 4)); - if (i == 0) return -1; // length octet '80' indefinite length - if (0 < i && i < 10) return i + 1; // including '8?' octet; - return -2; // malformed format - }; - - /** - * get hexadecimal string for ASN.1 L(length) bytes - * @name getHexOfL_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return {String} hexadecimal string for ASN.1 L(length) bytes - */ - this.getHexOfL_AtObj = function(s, pos) { - var len = this.getByteLengthOfL_AtObj(s, pos); - if (len < 1) return ''; - return s.substring(pos + 2, pos + 2 + len * 2); - }; - - // getting ASN.1 length value at the position 'idx' of - // hexa decimal string 's'. - // - // f('3082025b02...', 0) ... 82025b ... ??? - // f('020100', 0) ... 01 ... 1 - // f('0203001...', 0) ... 03 ... 3 - // f('02818003...', 0) ... 8180 ... 128 - /** - * get integer value of ASN.1 length for ASN.1 data - * @name getIntOfL_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return ASN.1 L(length) integer value - */ - this.getIntOfL_AtObj = function(s, pos) { - var hLength = this.getHexOfL_AtObj(s, pos); - if (hLength == '') return -1; - var bi; - if (parseInt(hLength.substring(0, 1)) < 8) { - bi = new BigInteger(hLength, 16); - } else { - bi = new BigInteger(hLength.substring(2), 16); - } - return bi.intValue(); - }; - - /** - * get ASN.1 value starting string position for ASN.1 object refered by index 'idx'. - * @name getStartPosOfV_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - */ - this.getStartPosOfV_AtObj = function(s, pos) { - var l_len = this.getByteLengthOfL_AtObj(s, pos); - if (l_len < 0) return l_len; - return pos + (l_len + 1) * 2; - }; - - /** - * get hexadecimal string of ASN.1 V(value) - * @name getHexOfV_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return {String} hexadecimal string of ASN.1 value. - */ - this.getHexOfV_AtObj = function(s, pos) { - var pos1 = this.getStartPosOfV_AtObj(s, pos); - var len = this.getIntOfL_AtObj(s, pos); - return s.substring(pos1, pos1 + len * 2); - }; - - /** - * get hexadecimal string of ASN.1 TLV at - * @name getHexOfTLV_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return {String} hexadecimal string of ASN.1 TLV. - * @since 1.1 - */ - this.getHexOfTLV_AtObj = function(s, pos) { - var hT = s.substr(pos, 2); - var hL = this.getHexOfL_AtObj(s, pos); - var hV = this.getHexOfV_AtObj(s, pos); - return hT + hL + hV; - }; - - /** - * get next sibling starting index for ASN.1 object string - * @name getPosOfNextSibling_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} pos string index - * @return next sibling starting index for ASN.1 object string - */ - this.getPosOfNextSibling_AtObj = function(s, pos) { - var pos1 = this.getStartPosOfV_AtObj(s, pos); - var len = this.getIntOfL_AtObj(s, pos); - return pos1 + len * 2; - }; - - /** - * get array of indexes of child ASN.1 objects - * @name getPosArrayOfChildren_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} s hexadecimal string of ASN.1 DER encoded data - * @param {Number} start string index of ASN.1 object - * @return {Array of Number} array of indexes for childen of ASN.1 objects - */ - this.getPosArrayOfChildren_AtObj = function(h, pos) { - var a = new Array(); - var p0 = this.getStartPosOfV_AtObj(h, pos); - a.push(p0); - - var len = this.getIntOfL_AtObj(h, pos); - var p = p0; - var k = 0; - while (1) { - var pNext = this.getPosOfNextSibling_AtObj(h, p); - if (pNext == null || (pNext - p0 >= (len * 2))) break; - if (k >= 200) break; - - a.push(pNext); - p = pNext; - - k++; - } - - return a; - }; - - /** - * get string index of nth child object of ASN.1 object refered by h, idx - * @name getNthChildIndex_AtObj - * @memberOf ASN1HEX - * @function - * @param {String} h hexadecimal string of ASN.1 DER encoded data - * @param {Number} idx start string index of ASN.1 object - * @param {Number} nth for child - * @return {Number} string index of nth child. - * @since 1.1 - */ - this.getNthChildIndex_AtObj = function(h, idx, nth) { - var a = this.getPosArrayOfChildren_AtObj(h, idx); - return a[nth]; - }; - - // ========== decendant methods ============================== - /** - * get string index of nth child object of ASN.1 object refered by h, idx - * @name getDecendantIndexByNthList - * @memberOf ASN1HEX - * @function - * @param {String} h hexadecimal string of ASN.1 DER encoded data - * @param {Number} currentIndex start string index of ASN.1 object - * @param {Array of Number} nthList array list of nth - * @return {Number} string index refered by nthList - * @since 1.1 - * @example - * The "nthList" is a index list of structured ASN.1 object - * reference. Here is a sample structure and "nthList"s which - * refers each objects. - * - * SQUENCE - - * SEQUENCE - [0] - * IA5STRING 000 - [0, 0] - * UTF8STRING 001 - [0, 1] - * SET - [1] - * IA5STRING 010 - [1, 0] - * UTF8STRING 011 - [1, 1] - */ - this.getDecendantIndexByNthList = function(h, currentIndex, nthList) { - if (nthList.length == 0) { - return currentIndex; - } - var firstNth = nthList.shift(); - var a = this.getPosArrayOfChildren_AtObj(h, currentIndex); - return this.getDecendantIndexByNthList(h, a[firstNth], nthList); - }; - - /** - * get hexadecimal string of ASN.1 TLV refered by current index and nth index list. - * @name getDecendantHexTLVByNthList - * @memberOf ASN1HEX - * @function - * @param {String} h hexadecimal string of ASN.1 DER encoded data - * @param {Number} currentIndex start string index of ASN.1 object - * @param {Array of Number} nthList array list of nth - * @return {Number} hexadecimal string of ASN.1 TLV refered by nthList - * @since 1.1 - */ - this.getDecendantHexTLVByNthList = function(h, currentIndex, nthList) { - var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); - return this.getHexOfTLV_AtObj(h, idx); - }; - - /** - * get hexadecimal string of ASN.1 V refered by current index and nth index list. - * @name getDecendantHexVByNthList - * @memberOf ASN1HEX - * @function - * @param {String} h hexadecimal string of ASN.1 DER encoded data - * @param {Number} currentIndex start string index of ASN.1 object - * @param {Array of Number} nthList array list of nth - * @return {Number} hexadecimal string of ASN.1 V refered by nthList - * @since 1.1 - */ - this.getDecendantHexVByNthList = function(h, currentIndex, nthList) { - var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); - return this.getHexOfV_AtObj(h, idx); - }; -}; - -/* - * @since asn1hex 1.1.4 - */ -ASN1HEX.getVbyList = function(h, currentIndex, nthList, checkingTag) { - var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); - if (idx === undefined) { - throw "can't find nthList object"; - } - if (checkingTag !== undefined) { - if (h.substr(idx, 2) != checkingTag) { - throw "checking tag doesn't match: " + - h.substr(idx,2) + "!=" + checkingTag; - } - } - return this.getHexOfV_AtObj(h, idx); -}; - -/** - * get OID string from hexadecimal encoded value - * @name hextooidstr - * @memberOf ASN1HEX - * @function - * @param {String} hex hexadecmal string of ASN.1 DER encoded OID value - * @return {String} OID string (ex. '1.2.3.4.567') - * @since asn1hex 1.1.5 - */ -ASN1HEX.hextooidstr = function(hex) { - var zeroPadding = function(s, len) { - if (s.length >= len) return s; - return new Array(len - s.length + 1).join('0') + s; - }; - - var a = []; - - // a[0], a[1] - var hex0 = hex.substr(0, 2); - var i0 = parseInt(hex0, 16); - a[0] = new String(Math.floor(i0 / 40)); - a[1] = new String(i0 % 40); - - // a[2]..a[n] - var hex1 = hex.substr(2); - var b = []; - for (var i = 0; i < hex1.length / 2; i++) { - b.push(parseInt(hex1.substr(i * 2, 2), 16)); - } - var c = []; - var cbin = ""; - for (var i = 0; i < b.length; i++) { - if (b[i] & 0x80) { - cbin = cbin + zeroPadding((b[i] & 0x7f).toString(2), 7); - } else { - cbin = cbin + zeroPadding((b[i] & 0x7f).toString(2), 7); - c.push(new String(parseInt(cbin, 2))); - cbin = ""; - } - } - - var s = a.join("."); - if (c.length > 0) s = s + "." + c.join("."); - return s; -}; - -/*! x509-1.1.3.js (c) 2012-2014 Kenji Urushima | kjur.github.com/jsrsasign/license - */ -/* - * x509.js - X509 class to read subject public key from certificate. - * - * Copyright (c) 2010-2014 Kenji Urushima (kenji.urushima@gmail.com) - * - * This software is licensed under the terms of the MIT License. - * http://kjur.github.com/jsrsasign/license - * - * The above copyright and license notice shall be - * included in all copies or substantial portions of the Software. - */ - -/** - * @fileOverview - * @name x509-1.1.js - * @author Kenji Urushima kenji.urushima@gmail.com - * @version x509 1.1.3 (2014-May-17) - * @since jsrsasign 1.x.x - * @license MIT License - */ - -/* - * Depends: - * base64.js - * rsa.js - * asn1hex.js - */ - -/** - * X.509 certificate class.
- * @class X.509 certificate class - * @property {RSAKey} subjectPublicKeyRSA Tom Wu's RSAKey object - * @property {String} subjectPublicKeyRSA_hN hexadecimal string for modulus of RSA public key - * @property {String} subjectPublicKeyRSA_hE hexadecimal string for public exponent of RSA public key - * @property {String} hex hexacedimal string for X.509 certificate. - * @author Kenji Urushima - * @version 1.0.1 (08 May 2012) - * @see 'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/ - */ -function X509() { - this.subjectPublicKeyRSA = null; - this.subjectPublicKeyRSA_hN = null; - this.subjectPublicKeyRSA_hE = null; - this.hex = null; - - // ===== get basic fields from hex ===================================== - - /** - * get hexadecimal string of serialNumber field of certificate.
- * @name getSerialNumberHex - * @memberOf X509# - * @function - */ - this.getSerialNumberHex = function() { - return ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 1]); - }; - - /** - * get hexadecimal string of issuer field TLV of certificate.
- * @name getIssuerHex - * @memberOf X509# - * @function - */ - this.getIssuerHex = function() { - return ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 3]); - }; - - /** - * get string of issuer field of certificate.
- * @name getIssuerString - * @memberOf X509# - * @function - */ - this.getIssuerString = function() { - return X509.hex2dn(ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 3])); - }; - - /** - * get hexadecimal string of subject field of certificate.
- * @name getSubjectHex - * @memberOf X509# - * @function - */ - this.getSubjectHex = function() { - return ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 5]); - }; - - /** - * get string of subject field of certificate.
- * @name getSubjectString - * @memberOf X509# - * @function - */ - this.getSubjectString = function() { - return X509.hex2dn(ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 5])); - }; - - /** - * get notBefore field string of certificate.
- * @name getNotBefore - * @memberOf X509# - * @function - */ - this.getNotBefore = function() { - var s = ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 4, 0]); - s = s.replace(/(..)/g, "%$1"); - s = decodeURIComponent(s); - return s; - }; - - /** - * get notAfter field string of certificate.
- * @name getNotAfter - * @memberOf X509# - * @function - */ - this.getNotAfter = function() { - var s = ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 4, 1]); - s = s.replace(/(..)/g, "%$1"); - s = decodeURIComponent(s); - return s; - }; - - // ===== read certificate public key ========================== - - // ===== read certificate ===================================== - /** - * read PEM formatted X.509 certificate from string.
- * @name readCertPEM - * @memberOf X509# - * @function - * @param {String} sCertPEM string for PEM formatted X.509 certificate - */ - this.readCertPEM = function(sCertPEM) { - var hCert = X509.pemToHex(sCertPEM); - var a = X509.getPublicKeyHexArrayFromCertHex(hCert); - var rsa = new RSAKey(); - rsa.setPublic(a[0], a[1]); - this.subjectPublicKeyRSA = rsa; - this.subjectPublicKeyRSA_hN = a[0]; - this.subjectPublicKeyRSA_hE = a[1]; - this.hex = hCert; - }; - - this.readCertPEMWithoutRSAInit = function(sCertPEM) { - var hCert = X509.pemToHex(sCertPEM); - var a = X509.getPublicKeyHexArrayFromCertHex(hCert); - this.subjectPublicKeyRSA.setPublic(a[0], a[1]); - this.subjectPublicKeyRSA_hN = a[0]; - this.subjectPublicKeyRSA_hE = a[1]; - this.hex = hCert; - }; -}; - -X509.pemToBase64 = function(sCertPEM) { - var s = sCertPEM; - s = s.replace("-----BEGIN CERTIFICATE-----", ""); - s = s.replace("-----END CERTIFICATE-----", ""); - s = s.replace(/[ \n]+/g, ""); - return s; -}; - -X509.pemToHex = function(sCertPEM) { - var b64Cert = X509.pemToBase64(sCertPEM); - var hCert = b64tohex(b64Cert); - return hCert; -}; - -// NOTE: Without BITSTRING encapsulation. -X509.getSubjectPublicKeyPosFromCertHex = function(hCert) { - var pInfo = X509.getSubjectPublicKeyInfoPosFromCertHex(hCert); - if (pInfo == -1) return -1; - var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, pInfo); - if (a.length != 2) return -1; - var pBitString = a[1]; - if (hCert.substring(pBitString, pBitString + 2) != '03') return -1; - var pBitStringV = ASN1HEX.getStartPosOfV_AtObj(hCert, pBitString); - - if (hCert.substring(pBitStringV, pBitStringV + 2) != '00') return -1; - return pBitStringV + 2; -}; - -// NOTE: privateKeyUsagePeriod field of X509v2 not supported. -// NOTE: v1 and v3 supported -X509.getSubjectPublicKeyInfoPosFromCertHex = function(hCert) { - var pTbsCert = ASN1HEX.getStartPosOfV_AtObj(hCert, 0); - var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, pTbsCert); - if (a.length < 1) return -1; - if (hCert.substring(a[0], a[0] + 10) == "a003020102") { // v3 - if (a.length < 6) return -1; - return a[6]; - } else { - if (a.length < 5) return -1; - return a[5]; - } -}; - -X509.getPublicKeyHexArrayFromCertHex = function(hCert) { - var p = X509.getSubjectPublicKeyPosFromCertHex(hCert); - var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, p); - if (a.length != 2) return []; - var hN = ASN1HEX.getHexOfV_AtObj(hCert, a[0]); - var hE = ASN1HEX.getHexOfV_AtObj(hCert, a[1]); - if (hN != null && hE != null) { - return [hN, hE]; - } else { - return []; - } -}; - -X509.getHexTbsCertificateFromCert = function(hCert) { - var pTbsCert = ASN1HEX.getStartPosOfV_AtObj(hCert, 0); - return pTbsCert; -}; - -X509.getPublicKeyHexArrayFromCertPEM = function(sCertPEM) { - var hCert = X509.pemToHex(sCertPEM); - var a = X509.getPublicKeyHexArrayFromCertHex(hCert); - return a; -}; - -X509.hex2dn = function(hDN) { - var s = ""; - var a = ASN1HEX.getPosArrayOfChildren_AtObj(hDN, 0); - for (var i = 0; i < a.length; i++) { - var hRDN = ASN1HEX.getHexOfTLV_AtObj(hDN, a[i]); - s = s + "/" + X509.hex2rdn(hRDN); - } - return s; -}; - -X509.hex2rdn = function(hRDN) { - var hType = ASN1HEX.getDecendantHexTLVByNthList(hRDN, 0, [0, 0]); - var hValue = ASN1HEX.getDecendantHexVByNthList(hRDN, 0, [0, 1]); - var type = ""; - try { type = X509.DN_ATTRHEX[hType]; } catch (ex) { type = hType; } - hValue = hValue.replace(/(..)/g, "%$1"); - var value = decodeURIComponent(hValue); - return type + "=" + value; -}; - -X509.DN_ATTRHEX = { - "0603550406": "C", - "060355040a": "O", - "060355040b": "OU", - "0603550403": "CN", - "0603550405": "SN", - "0603550408": "ST", - "0603550407": "L", -}; - -/** - * get RSAKey/ECDSA public key object from PEM certificate string - * @name getPublicKeyFromCertPEM - * @memberOf X509 - * @function - * @param {String} sCertPEM PEM formatted RSA/ECDSA/DSA X.509 certificate - * @return returns RSAKey/KJUR.crypto.{ECDSA,DSA} object of public key - * @since x509 1.1.1 - * @description - * NOTE: DSA is also supported since x509 1.1.2. - */ -X509.getPublicKeyFromCertPEM = function(sCertPEM) { - var info = X509.getPublicKeyInfoPropOfCertPEM(sCertPEM); - - if (info.algoid == "2a864886f70d010101") { // RSA - var aRSA = KEYUTIL.parsePublicRawRSAKeyHex(info.keyhex); - var key = new RSAKey(); - key.setPublic(aRSA.n, aRSA.e); - return key; - } else if (info.algoid == "2a8648ce3d0201") { // ECC - var curveName = KJUR.crypto.OID.oidhex2name[info.algparam]; - var key = new KJUR.crypto.ECDSA({'curve': curveName, 'info': info.keyhex}); - key.setPublicKeyHex(info.keyhex); - return key; - } else if (info.algoid == "2a8648ce380401") { // DSA 1.2.840.10040.4.1 - var p = ASN1HEX.getVbyList(info.algparam, 0, [0], "02"); - var q = ASN1HEX.getVbyList(info.algparam, 0, [1], "02"); - var g = ASN1HEX.getVbyList(info.algparam, 0, [2], "02"); - var y = ASN1HEX.getHexOfV_AtObj(info.keyhex, 0); - y = y.substr(2); - var key = new KJUR.crypto.DSA(); - key.setPublic(new BigInteger(p, 16), - new BigInteger(q, 16), - new BigInteger(g, 16), - new BigInteger(y, 16)); - return key; - } else { - throw "unsupported key"; - } -}; - -/** - * get public key information from PEM certificate - * @name getPublicKeyInfoPropOfCertPEM - * @memberOf X509 - * @function - * @param {String} sCertPEM string of PEM formatted certificate - * @return {Hash} hash of information for public key - * @since x509 1.1.1 - * @description - * Resulted associative array has following properties: - *
    - *
  • algoid - hexadecimal string of OID of asymmetric key algorithm
  • - *
  • algparam - hexadecimal string of OID of ECC curve name or null
  • - *
  • keyhex - hexadecimal string of key in the certificate
  • - *
- * @since x509 1.1.1 - */ -X509.getPublicKeyInfoPropOfCertPEM = function(sCertPEM) { - var result = {}; - result.algparam = null; - var hCert = X509.pemToHex(sCertPEM); - - // 1. Certificate ASN.1 - var a1 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, 0); - if (a1.length != 3) - throw "malformed X.509 certificate PEM (code:001)"; // not 3 item of seq Cert - - // 2. tbsCertificate - if (hCert.substr(a1[0], 2) != "30") - throw "malformed X.509 certificate PEM (code:002)"; // tbsCert not seq - - var a2 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a1[0]); - - // 3. subjectPublicKeyInfo - if (a2.length < 7) - throw "malformed X.509 certificate PEM (code:003)"; // no subjPubKeyInfo - - var a3 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a2[6]); - - if (a3.length != 2) - throw "malformed X.509 certificate PEM (code:004)"; // not AlgId and PubKey - - // 4. AlgId - var a4 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a3[0]); - - if (a4.length != 2) - throw "malformed X.509 certificate PEM (code:005)"; // not 2 item in AlgId - - result.algoid = ASN1HEX.getHexOfV_AtObj(hCert, a4[0]); - - if (hCert.substr(a4[1], 2) == "06") { // EC - result.algparam = ASN1HEX.getHexOfV_AtObj(hCert, a4[1]); - } else if (hCert.substr(a4[1], 2) == "30") { // DSA - result.algparam = ASN1HEX.getHexOfTLV_AtObj(hCert, a4[1]); - } - - // 5. Public Key Hex - if (hCert.substr(a3[1], 2) != "03") - throw "malformed X.509 certificate PEM (code:006)"; // not bitstring - - var unusedBitAndKeyHex = ASN1HEX.getHexOfV_AtObj(hCert, a3[1]); - result.keyhex = unusedBitAndKeyHex.substr(2); - - return result; -}; - -/* - X509.prototype.readCertPEM = _x509_readCertPEM; - X509.prototype.readCertPEMWithoutRSAInit = _x509_readCertPEMWithoutRSAInit; - X509.prototype.getSerialNumberHex = _x509_getSerialNumberHex; - X509.prototype.getIssuerHex = _x509_getIssuerHex; - X509.prototype.getSubjectHex = _x509_getSubjectHex; - X509.prototype.getIssuerString = _x509_getIssuerString; - X509.prototype.getSubjectString = _x509_getSubjectString; - X509.prototype.getNotBefore = _x509_getNotBefore; - X509.prototype.getNotAfter = _x509_getNotAfter; -*/ -/*! crypto-1.1.5.js (c) 2013 Kenji Urushima | kjur.github.com/jsrsasign/license - */ -/* - * crypto.js - Cryptographic Algorithm Provider class - * - * Copyright (c) 2013 Kenji Urushima (kenji.urushima@gmail.com) - * - * This software is licensed under the terms of the MIT License. - * http://kjur.github.com/jsrsasign/license - * - * The above copyright and license notice shall be - * included in all copies or substantial portions of the Software. - */ - -/** - * @fileOverview - * @name crypto-1.1.js - * @author Kenji Urushima kenji.urushima@gmail.com - * @version 1.1.5 (2013-Oct-06) - * @since jsrsasign 2.2 - * @license MIT License - */ - -/** - * kjur's class library name space - * @name KJUR - * @namespace kjur's class library name space - */ -if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; -/** - * kjur's cryptographic algorithm provider library name space - *

- * This namespace privides following crytpgrahic classes. - *

    - *
  • {@link KJUR.crypto.MessageDigest} - Java JCE(cryptograhic extension) style MessageDigest class
  • - *
  • {@link KJUR.crypto.Signature} - Java JCE(cryptograhic extension) style Signature class
  • - *
  • {@link KJUR.crypto.Util} - cryptographic utility functions and properties
  • - *
- * NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. - *

- * @name KJUR.crypto - * @namespace - */ -if (typeof KJUR.crypto == "undefined" || !KJUR.crypto) KJUR.crypto = {}; - -/** - * static object for cryptographic function utilities - * @name KJUR.crypto.Util - * @class static object for cryptographic function utilities - * @property {Array} DIGESTINFOHEAD PKCS#1 DigestInfo heading hexadecimal bytes for each hash algorithms - * @property {Array} DEFAULTPROVIDER associative array of default provider name for each hash and signature algorithms - * @description - */ -KJUR.crypto.Util = new function() { - this.DIGESTINFOHEAD = { - 'sha1': "3021300906052b0e03021a05000414", - 'sha224': "302d300d06096086480165030402040500041c", - 'sha256': "3031300d060960864801650304020105000420", - 'sha384': "3041300d060960864801650304020205000430", - 'sha512': "3051300d060960864801650304020305000440", - 'md2': "3020300c06082a864886f70d020205000410", - 'md5': "3020300c06082a864886f70d020505000410", - 'ripemd160': "3021300906052b2403020105000414", - }; - - /* - * @since crypto 1.1.1 - */ - this.DEFAULTPROVIDER = { - 'md5': 'cryptojs', - 'sha1': 'cryptojs', - 'sha224': 'cryptojs', - 'sha256': 'cryptojs', - 'sha384': 'cryptojs', - 'sha512': 'cryptojs', - 'ripemd160': 'cryptojs', - 'hmacmd5': 'cryptojs', - 'hmacsha1': 'cryptojs', - 'hmacsha224': 'cryptojs', - 'hmacsha256': 'cryptojs', - 'hmacsha384': 'cryptojs', - 'hmacsha512': 'cryptojs', - 'hmacripemd160': 'cryptojs', - - 'MD5withRSA': 'cryptojs/jsrsa', - 'SHA1withRSA': 'cryptojs/jsrsa', - 'SHA224withRSA': 'cryptojs/jsrsa', - 'SHA256withRSA': 'cryptojs/jsrsa', - 'SHA384withRSA': 'cryptojs/jsrsa', - 'SHA512withRSA': 'cryptojs/jsrsa', - 'RIPEMD160withRSA': 'cryptojs/jsrsa', - - 'MD5withECDSA': 'cryptojs/jsrsa', - 'SHA1withECDSA': 'cryptojs/jsrsa', - 'SHA224withECDSA': 'cryptojs/jsrsa', - 'SHA256withECDSA': 'cryptojs/jsrsa', - 'SHA384withECDSA': 'cryptojs/jsrsa', - 'SHA512withECDSA': 'cryptojs/jsrsa', - 'RIPEMD160withECDSA': 'cryptojs/jsrsa', - - 'SHA1withDSA': 'cryptojs/jsrsa', - 'SHA224withDSA': 'cryptojs/jsrsa', - 'SHA256withDSA': 'cryptojs/jsrsa', - - 'MD5withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA1withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA224withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA256withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA384withRSAandMGF1': 'cryptojs/jsrsa', - 'SHA512withRSAandMGF1': 'cryptojs/jsrsa', - 'RIPEMD160withRSAandMGF1': 'cryptojs/jsrsa', - }; - - /* - * @since crypto 1.1.2 - */ - this.CRYPTOJSMESSAGEDIGESTNAME = { - 'md5': 'CryptoJS.algo.MD5', - 'sha1': 'CryptoJS.algo.SHA1', - 'sha224': 'CryptoJS.algo.SHA224', - 'sha256': 'CryptoJS.algo.SHA256', - 'sha384': 'CryptoJS.algo.SHA384', - 'sha512': 'CryptoJS.algo.SHA512', - 'ripemd160': 'CryptoJS.algo.RIPEMD160' - }; - - /** - * get hexadecimal DigestInfo - * @name getDigestInfoHex - * @memberOf KJUR.crypto.Util - * @function - * @param {String} hHash hexadecimal hash value - * @param {String} alg hash algorithm name (ex. 'sha1') - * @return {String} hexadecimal string DigestInfo ASN.1 structure - */ - this.getDigestInfoHex = function(hHash, alg) { - if (typeof this.DIGESTINFOHEAD[alg] == "undefined") - throw "alg not supported in Util.DIGESTINFOHEAD: " + alg; - return this.DIGESTINFOHEAD[alg] + hHash; - }; - - /** - * get PKCS#1 padded hexadecimal DigestInfo - * @name getPaddedDigestInfoHex - * @memberOf KJUR.crypto.Util - * @function - * @param {String} hHash hexadecimal hash value of message to be signed - * @param {String} alg hash algorithm name (ex. 'sha1') - * @param {Integer} keySize key bit length (ex. 1024) - * @return {String} hexadecimal string of PKCS#1 padded DigestInfo - */ - this.getPaddedDigestInfoHex = function(hHash, alg, keySize) { - var hDigestInfo = this.getDigestInfoHex(hHash, alg); - var pmStrLen = keySize / 4; // minimum PM length - - if (hDigestInfo.length + 22 > pmStrLen) // len(0001+ff(*8)+00+hDigestInfo)=22 - throw "key is too short for SigAlg: keylen=" + keySize + "," + alg; - - var hHead = "0001"; - var hTail = "00" + hDigestInfo; - var hMid = ""; - var fLen = pmStrLen - hHead.length - hTail.length; - for (var i = 0; i < fLen; i += 2) { - hMid += "ff"; - } - var hPaddedMessage = hHead + hMid + hTail; - return hPaddedMessage; - }; - - /** - * get hexadecimal hash of string with specified algorithm - * @name hashString - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @param {String} alg hash algorithm name - * @return {String} hexadecimal string of hash value - * @since 1.1.1 - */ - this.hashString = function(s, alg) { - var md = new KJUR.crypto.MessageDigest({'alg': alg}); - return md.digestString(s); - }; - - /** - * get hexadecimal hash of hexadecimal string with specified algorithm - * @name hashHex - * @memberOf KJUR.crypto.Util - * @function - * @param {String} sHex input hexadecimal string to be hashed - * @param {String} alg hash algorithm name - * @return {String} hexadecimal string of hash value - * @since 1.1.1 - */ - this.hashHex = function(sHex, alg) { - var md = new KJUR.crypto.MessageDigest({'alg': alg}); - return md.digestHex(sHex); - }; - - /** - * get hexadecimal SHA1 hash of string - * @name sha1 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.sha1 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha1', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - /** - * get hexadecimal SHA256 hash of string - * @name sha256 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.sha256 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha256', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - this.sha256Hex = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha256', 'prov':'cryptojs'}); - return md.digestHex(s); - }; - - /** - * get hexadecimal SHA512 hash of string - * @name sha512 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.sha512 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha512', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - this.sha512Hex = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'sha512', 'prov':'cryptojs'}); - return md.digestHex(s); - }; - - /** - * get hexadecimal MD5 hash of string - * @name md5 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.md5 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'md5', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - /** - * get hexadecimal RIPEMD160 hash of string - * @name ripemd160 - * @memberOf KJUR.crypto.Util - * @function - * @param {String} s input string to be hashed - * @return {String} hexadecimal string of hash value - * @since 1.0.3 - */ - this.ripemd160 = function(s) { - var md = new KJUR.crypto.MessageDigest({'alg':'ripemd160', 'prov':'cryptojs'}); - return md.digestString(s); - }; - - /* - * @since 1.1.2 - */ - this.getCryptoJSMDByName = function(s) { - - }; -}; - -/** - * MessageDigest class which is very similar to java.security.MessageDigest class - * @name KJUR.crypto.MessageDigest - * @class MessageDigest class which is very similar to java.security.MessageDigest class - * @param {Array} params parameters for constructor - * @description - *
- * Currently this supports following algorithm and providers combination: - *
    - *
  • md5 - cryptojs
  • - *
  • sha1 - cryptojs
  • - *
  • sha224 - cryptojs
  • - *
  • sha256 - cryptojs
  • - *
  • sha384 - cryptojs
  • - *
  • sha512 - cryptojs
  • - *
  • ripemd160 - cryptojs
  • - *
  • sha256 - sjcl (NEW from crypto.js 1.0.4)
  • - *
- * @example - * // CryptoJS provider sample - * <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/core.js"></script> - * <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/sha1.js"></script> - * <script src="crypto-1.0.js"></script> - * var md = new KJUR.crypto.MessageDigest({alg: "sha1", prov: "cryptojs"}); - * md.updateString('aaa') - * var mdHex = md.digest() - * - * // SJCL(Stanford JavaScript Crypto Library) provider sample - * <script src="http://bitwiseshiftleft.github.io/sjcl/sjcl.js"></script> - * <script src="crypto-1.0.js"></script> - * var md = new KJUR.crypto.MessageDigest({alg: "sha256", prov: "sjcl"}); // sjcl supports sha256 only - * md.updateString('aaa') - * var mdHex = md.digest() - */ -KJUR.crypto.MessageDigest = function(params) { - var md = null; - var algName = null; - var provName = null; - - /** - * set hash algorithm and provider - * @name setAlgAndProvider - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} alg hash algorithm name - * @param {String} prov provider name - * @description - * @example - * // for SHA1 - * md.setAlgAndProvider('sha1', 'cryptojs'); - * // for RIPEMD160 - * md.setAlgAndProvider('ripemd160', 'cryptojs'); - */ - this.setAlgAndProvider = function(alg, prov) { - if (alg != null && prov === undefined) prov = KJUR.crypto.Util.DEFAULTPROVIDER[alg]; - - // for cryptojs - if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(alg) != -1 && - prov == 'cryptojs') { - try { - this.md = eval(KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[alg]).create(); - } catch (ex) { - throw "setAlgAndProvider hash alg set fail alg=" + alg + "/" + ex; - } - this.updateString = function(str) { - this.md.update(str); - }; - this.updateHex = function(hex) { - var wHex = CryptoJS.enc.Hex.parse(hex); - this.md.update(wHex); - }; - this.digest = function() { - var hash = this.md.finalize(); - return hash.toString(CryptoJS.enc.Hex); - }; - this.digestString = function(str) { - this.updateString(str); - return this.digest(); - }; - this.digestHex = function(hex) { - this.updateHex(hex); - return this.digest(); - }; - } - if (':sha256:'.indexOf(alg) != -1 && - prov == 'sjcl') { - try { - this.md = new sjcl.hash.sha256(); - } catch (ex) { - throw "setAlgAndProvider hash alg set fail alg=" + alg + "/" + ex; - } - this.updateString = function(str) { - this.md.update(str); - }; - this.updateHex = function(hex) { - var baHex = sjcl.codec.hex.toBits(hex); - this.md.update(baHex); - }; - this.digest = function() { - var hash = this.md.finalize(); - return sjcl.codec.hex.fromBits(hash); - }; - this.digestString = function(str) { - this.updateString(str); - return this.digest(); - }; - this.digestHex = function(hex) { - this.updateHex(hex); - return this.digest(); - }; - } - }; - - /** - * update digest by specified string - * @name updateString - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} str string to update - * @description - * @example - * md.updateString('New York'); - */ - this.updateString = function(str) { - throw "updateString(str) not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - /** - * update digest by specified hexadecimal string - * @name updateHex - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} hex hexadecimal string to update - * @description - * @example - * md.updateHex('0afe36'); - */ - this.updateHex = function(hex) { - throw "updateHex(hex) not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - /** - * completes hash calculation and returns hash result - * @name digest - * @memberOf KJUR.crypto.MessageDigest - * @function - * @description - * @example - * md.digest() - */ - this.digest = function() { - throw "digest() not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - /** - * performs final update on the digest using string, then completes the digest computation - * @name digestString - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} str string to final update - * @description - * @example - * md.digestString('aaa') - */ - this.digestString = function(str) { - throw "digestString(str) not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - /** - * performs final update on the digest using hexadecimal string, then completes the digest computation - * @name digestHex - * @memberOf KJUR.crypto.MessageDigest - * @function - * @param {String} hex hexadecimal string to final update - * @description - * @example - * md.digestHex('0f2abd') - */ - this.digestHex = function(hex) { - throw "digestHex(hex) not supported for this alg/prov: " + this.algName + "/" + this.provName; - }; - - if (params !== undefined) { - if (params['alg'] !== undefined) { - this.algName = params['alg']; - if (params['prov'] === undefined) - this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; - this.setAlgAndProvider(this.algName, this.provName); - } - } -}; - -/** - * Mac(Message Authentication Code) class which is very similar to java.security.Mac class - * @name KJUR.crypto.Mac - * @class Mac class which is very similar to java.security.Mac class - * @param {Array} params parameters for constructor - * @description - *
- * Currently this supports following algorithm and providers combination: - *
    - *
  • hmacmd5 - cryptojs
  • - *
  • hmacsha1 - cryptojs
  • - *
  • hmacsha224 - cryptojs
  • - *
  • hmacsha256 - cryptojs
  • - *
  • hmacsha384 - cryptojs
  • - *
  • hmacsha512 - cryptojs
  • - *
- * NOTE: HmacSHA224 and HmacSHA384 issue was fixed since jsrsasign 4.1.4. - * Please use 'ext/cryptojs-312-core-fix*.js' instead of 'core.js' of original CryptoJS - * to avoid those issue. - * @example - * var mac = new KJUR.crypto.Mac({alg: "HmacSHA1", prov: "cryptojs", "pass": "pass"}); - * mac.updateString('aaa') - * var macHex = md.doFinal() - */ -KJUR.crypto.Mac = function(params) { - var mac = null; - var pass = null; - var algName = null; - var provName = null; - var algProv = null; - - this.setAlgAndProvider = function(alg, prov) { - if (alg == null) alg = "hmacsha1"; - - alg = alg.toLowerCase(); - if (alg.substr(0, 4) != "hmac") { - throw "setAlgAndProvider unsupported HMAC alg: " + alg; - } - - if (prov === undefined) prov = KJUR.crypto.Util.DEFAULTPROVIDER[alg]; - this.algProv = alg + "/" + prov; - - var hashAlg = alg.substr(4); - - // for cryptojs - if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(hashAlg) != -1 && - prov == 'cryptojs') { - try { - var mdObj = eval(KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[hashAlg]); - this.mac = CryptoJS.algo.HMAC.create(mdObj, this.pass); - } catch (ex) { - throw "setAlgAndProvider hash alg set fail hashAlg=" + hashAlg + "/" + ex; - } - this.updateString = function(str) { - this.mac.update(str); - }; - this.updateHex = function(hex) { - var wHex = CryptoJS.enc.Hex.parse(hex); - this.mac.update(wHex); - }; - this.doFinal = function() { - var hash = this.mac.finalize(); - return hash.toString(CryptoJS.enc.Hex); - }; - this.doFinalString = function(str) { - this.updateString(str); - return this.doFinal(); - }; - this.doFinalHex = function(hex) { - this.updateHex(hex); - return this.doFinal(); - }; - } - }; - - /** - * update digest by specified string - * @name updateString - * @memberOf KJUR.crypto.Mac - * @function - * @param {String} str string to update - * @description - * @example - * md.updateString('New York'); - */ - this.updateString = function(str) { - throw "updateString(str) not supported for this alg/prov: " + this.algProv; - }; - - /** - * update digest by specified hexadecimal string - * @name updateHex - * @memberOf KJUR.crypto.Mac - * @function - * @param {String} hex hexadecimal string to update - * @description - * @example - * md.updateHex('0afe36'); - */ - this.updateHex = function(hex) { - throw "updateHex(hex) not supported for this alg/prov: " + this.algProv; - }; - - /** - * completes hash calculation and returns hash result - * @name doFinal - * @memberOf KJUR.crypto.Mac - * @function - * @description - * @example - * md.digest() - */ - this.doFinal = function() { - throw "digest() not supported for this alg/prov: " + this.algProv; - }; - - /** - * performs final update on the digest using string, then completes the digest computation - * @name doFinalString - * @memberOf KJUR.crypto.Mac - * @function - * @param {String} str string to final update - * @description - * @example - * md.digestString('aaa') - */ - this.doFinalString = function(str) { - throw "digestString(str) not supported for this alg/prov: " + this.algProv; - }; - - /** - * performs final update on the digest using hexadecimal string, - * then completes the digest computation - * @name doFinalHex - * @memberOf KJUR.crypto.Mac - * @function - * @param {String} hex hexadecimal string to final update - * @description - * @example - * md.digestHex('0f2abd') - */ - this.doFinalHex = function(hex) { - throw "digestHex(hex) not supported for this alg/prov: " + this.algProv; - }; - - if (params !== undefined) { - if (params['pass'] !== undefined) { - this.pass = params['pass']; - } - if (params['alg'] !== undefined) { - this.algName = params['alg']; - if (params['prov'] === undefined) - this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; - this.setAlgAndProvider(this.algName, this.provName); - } - } -}; - -/** - * Signature class which is very similar to java.security.Signature class - * @name KJUR.crypto.Signature - * @class Signature class which is very similar to java.security.Signature class - * @param {Array} params parameters for constructor - * @property {String} state Current state of this signature object whether 'SIGN', 'VERIFY' or null - * @description - *
- * As for params of constructor's argument, it can be specify following attributes: - *
    - *
  • alg - signature algorithm name (ex. {MD5,SHA1,SHA224,SHA256,SHA384,SHA512,RIPEMD160}with{RSA,ECDSA,DSA})
  • - *
  • provider - currently 'cryptojs/jsrsa' only
  • - *
- *

SUPPORTED ALGORITHMS AND PROVIDERS

- * This Signature class supports following signature algorithm and provider names: - *
    - *
  • MD5withRSA - cryptojs/jsrsa
  • - *
  • SHA1withRSA - cryptojs/jsrsa
  • - *
  • SHA224withRSA - cryptojs/jsrsa
  • - *
  • SHA256withRSA - cryptojs/jsrsa
  • - *
  • SHA384withRSA - cryptojs/jsrsa
  • - *
  • SHA512withRSA - cryptojs/jsrsa
  • - *
  • RIPEMD160withRSA - cryptojs/jsrsa
  • - *
  • MD5withECDSA - cryptojs/jsrsa
  • - *
  • SHA1withECDSA - cryptojs/jsrsa
  • - *
  • SHA224withECDSA - cryptojs/jsrsa
  • - *
  • SHA256withECDSA - cryptojs/jsrsa
  • - *
  • SHA384withECDSA - cryptojs/jsrsa
  • - *
  • SHA512withECDSA - cryptojs/jsrsa
  • - *
  • RIPEMD160withECDSA - cryptojs/jsrsa
  • - *
  • MD5withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA1withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA224withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA256withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA384withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA512withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • RIPEMD160withRSAandMGF1 - cryptojs/jsrsa
  • - *
  • SHA1withDSA - cryptojs/jsrsa
  • - *
  • SHA224withDSA - cryptojs/jsrsa
  • - *
  • SHA256withDSA - cryptojs/jsrsa
  • - *
- * Here are supported elliptic cryptographic curve names and their aliases for ECDSA: - *
    - *
  • secp256k1
  • - *
  • secp256r1, NIST P-256, P-256, prime256v1
  • - *
  • secp384r1, NIST P-384, P-384
  • - *
- * NOTE1: DSA signing algorithm is also supported since crypto 1.1.5. - *

EXAMPLES

- * @example - * // RSA signature generation - * var sig = new KJUR.crypto.Signature({"alg": "SHA1withRSA"}); - * sig.init(prvKeyPEM); - * sig.updateString('aaa'); - * var hSigVal = sig.sign(); - * - * // DSA signature validation - * var sig2 = new KJUR.crypto.Signature({"alg": "SHA1withDSA"}); - * sig2.init(certPEM); - * sig.updateString('aaa'); - * var isValid = sig2.verify(hSigVal); - * - * // ECDSA signing - * var sig = new KJUR.crypto.Signature({'alg':'SHA1withECDSA'}); - * sig.init(prvKeyPEM); - * sig.updateString('aaa'); - * var sigValueHex = sig.sign(); - * - * // ECDSA verifying - * var sig2 = new KJUR.crypto.Signature({'alg':'SHA1withECDSA'}); - * sig.init(certPEM); - * sig.updateString('aaa'); - * var isValid = sig.verify(sigValueHex); - */ -KJUR.crypto.Signature = function(params) { - var prvKey = null; // RSAKey/KJUR.crypto.{ECDSA,DSA} object for signing - var pubKey = null; // RSAKey/KJUR.crypto.{ECDSA,DSA} object for verifying - - var md = null; // KJUR.crypto.MessageDigest object - var sig = null; - var algName = null; - var provName = null; - var algProvName = null; - var mdAlgName = null; - var pubkeyAlgName = null; // rsa,ecdsa,rsaandmgf1(=rsapss) - var state = null; - var pssSaltLen = -1; - var initParams = null; - - var sHashHex = null; // hex hash value for hex - var hDigestInfo = null; - var hPaddedDigestInfo = null; - var hSign = null; - - this._setAlgNames = function() { - if (this.algName.match(/^(.+)with(.+)$/)) { - this.mdAlgName = RegExp.$1.toLowerCase(); - this.pubkeyAlgName = RegExp.$2.toLowerCase(); - } - }; - - this._zeroPaddingOfSignature = function(hex, bitLength) { - var s = ""; - var nZero = bitLength / 4 - hex.length; - for (var i = 0; i < nZero; i++) { - s = s + "0"; - } - return s + hex; - }; - - /** - * set signature algorithm and provider - * @name setAlgAndProvider - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} alg signature algorithm name - * @param {String} prov provider name - * @description - * @example - * md.setAlgAndProvider('SHA1withRSA', 'cryptojs/jsrsa'); - */ - this.setAlgAndProvider = function(alg, prov) { - this._setAlgNames(); - if (prov != 'cryptojs/jsrsa') - throw "provider not supported: " + prov; - - if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(this.mdAlgName) != -1) { - try { - this.md = new KJUR.crypto.MessageDigest({'alg':this.mdAlgName}); - } catch (ex) { - throw "setAlgAndProvider hash alg set fail alg=" + - this.mdAlgName + "/" + ex; - } - - this.init = function(keyparam, pass) { - var keyObj = null; - try { - if (pass === undefined) { - keyObj = KEYUTIL.getKey(keyparam); - } else { - keyObj = KEYUTIL.getKey(keyparam, pass); - } - } catch (ex) { - throw "init failed:" + ex; - } - - if (keyObj.isPrivate === true) { - this.prvKey = keyObj; - this.state = "SIGN"; - } else if (keyObj.isPublic === true) { - this.pubKey = keyObj; - this.state = "VERIFY"; - } else { - throw "init failed.:" + keyObj; - } - }; - - this.initSign = function(params) { - if (typeof params['ecprvhex'] == 'string' && - typeof params['eccurvename'] == 'string') { - this.ecprvhex = params['ecprvhex']; - this.eccurvename = params['eccurvename']; - } else { - this.prvKey = params; - } - this.state = "SIGN"; - }; - - this.initVerifyByPublicKey = function(params) { - if (typeof params['ecpubhex'] == 'string' && - typeof params['eccurvename'] == 'string') { - this.ecpubhex = params['ecpubhex']; - this.eccurvename = params['eccurvename']; - } else if (params instanceof KJUR.crypto.ECDSA) { - this.pubKey = params; - } else if (params instanceof RSAKey) { - this.pubKey = params; - } - this.state = "VERIFY"; - }; - - this.initVerifyByCertificatePEM = function(certPEM) { - var x509 = new X509(); - x509.readCertPEM(certPEM); - this.pubKey = x509.subjectPublicKeyRSA; - this.state = "VERIFY"; - }; - - this.updateString = function(str) { - this.md.updateString(str); - }; - this.updateHex = function(hex) { - this.md.updateHex(hex); - }; - - this.sign = function() { - this.sHashHex = this.md.digest(); - if (typeof this.ecprvhex != "undefined" && - typeof this.eccurvename != "undefined") { - var ec = new KJUR.crypto.ECDSA({'curve': this.eccurvename}); - this.hSign = ec.signHex(this.sHashHex, this.ecprvhex); - } else if (this.pubkeyAlgName == "rsaandmgf1") { - this.hSign = this.prvKey.signWithMessageHashPSS(this.sHashHex, - this.mdAlgName, - this.pssSaltLen); - } else if (this.pubkeyAlgName == "rsa") { - this.hSign = this.prvKey.signWithMessageHash(this.sHashHex, - this.mdAlgName); - } else if (this.prvKey instanceof KJUR.crypto.ECDSA) { - this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); - } else if (this.prvKey instanceof KJUR.crypto.DSA) { - this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); - } else { - throw "Signature: unsupported public key alg: " + this.pubkeyAlgName; - } - return this.hSign; - }; - this.signString = function(str) { - this.updateString(str); - return this.sign(); - }; - this.signHex = function(hex) { - this.updateHex(hex); - return this.sign(); - }; - this.verify = function(hSigVal) { - this.sHashHex = this.md.digest(); - if (typeof this.ecpubhex != "undefined" && - typeof this.eccurvename != "undefined") { - var ec = new KJUR.crypto.ECDSA({curve: this.eccurvename}); - return ec.verifyHex(this.sHashHex, hSigVal, this.ecpubhex); - } else if (this.pubkeyAlgName == "rsaandmgf1") { - return this.pubKey.verifyWithMessageHashPSS(this.sHashHex, hSigVal, - this.mdAlgName, - this.pssSaltLen); - } else if (this.pubkeyAlgName == "rsa") { - return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); - } else if (this.pubKey instanceof KJUR.crypto.ECDSA) { - return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); - } else if (this.pubKey instanceof KJUR.crypto.DSA) { - return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); - } else { - throw "Signature: unsupported public key alg: " + this.pubkeyAlgName; - } - }; - } - }; - - /** - * Initialize this object for signing or verifying depends on key - * @name init - * @memberOf KJUR.crypto.Signature - * @function - * @param {Object} key specifying public or private key as plain/encrypted PKCS#5/8 PEM file, certificate PEM or {@link RSAKey}, {@link KJUR.crypto.DSA} or {@link KJUR.crypto.ECDSA} object - * @param {String} pass (OPTION) passcode for encrypted private key - * @since crypto 1.1.3 - * @description - * This method is very useful initialize method for Signature class since - * you just specify key then this method will automatically initialize it - * using {@link KEYUTIL.getKey} method. - * As for 'key', following argument type are supported: - *
signing
- *
    - *
  • PEM formatted PKCS#8 encrypted RSA/ECDSA private key concluding "BEGIN ENCRYPTED PRIVATE KEY"
  • - *
  • PEM formatted PKCS#5 encrypted RSA/DSA private key concluding "BEGIN RSA/DSA PRIVATE KEY" and ",ENCRYPTED"
  • - *
  • PEM formatted PKCS#8 plain RSA/ECDSA private key concluding "BEGIN PRIVATE KEY"
  • - *
  • PEM formatted PKCS#5 plain RSA/DSA private key concluding "BEGIN RSA/DSA PRIVATE KEY" without ",ENCRYPTED"
  • - *
  • RSAKey object of private key
  • - *
  • KJUR.crypto.ECDSA object of private key
  • - *
  • KJUR.crypto.DSA object of private key
  • - *
- *
verification
- *
    - *
  • PEM formatted PKCS#8 RSA/EC/DSA public key concluding "BEGIN PUBLIC KEY"
  • - *
  • PEM formatted X.509 certificate with RSA/EC/DSA public key concluding - * "BEGIN CERTIFICATE", "BEGIN X509 CERTIFICATE" or "BEGIN TRUSTED CERTIFICATE".
  • - *
  • RSAKey object of public key
  • - *
  • KJUR.crypto.ECDSA object of public key
  • - *
  • KJUR.crypto.DSA object of public key
  • - *
- * @example - * sig.init(sCertPEM) - */ - this.init = function(key, pass) { - throw "init(key, pass) not supported for this alg:prov=" + - this.algProvName; - }; - - /** - * Initialize this object for verifying with a public key - * @name initVerifyByPublicKey - * @memberOf KJUR.crypto.Signature - * @function - * @param {Object} param RSAKey object of public key or associative array for ECDSA - * @since 1.0.2 - * @deprecated from crypto 1.1.5. please use init() method instead. - * @description - * Public key information will be provided as 'param' parameter and the value will be - * following: - *
    - *
  • {@link RSAKey} object for RSA verification
  • - *
  • associative array for ECDSA verification - * (ex. {'ecpubhex': '041f..', 'eccurvename': 'secp256r1'}) - *
  • - *
- * @example - * sig.initVerifyByPublicKey(rsaPrvKey) - */ - this.initVerifyByPublicKey = function(rsaPubKey) { - throw "initVerifyByPublicKey(rsaPubKeyy) not supported for this alg:prov=" + - this.algProvName; - }; - - /** - * Initialize this object for verifying with a certficate - * @name initVerifyByCertificatePEM - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} certPEM PEM formatted string of certificate - * @since 1.0.2 - * @deprecated from crypto 1.1.5. please use init() method instead. - * @description - * @example - * sig.initVerifyByCertificatePEM(certPEM) - */ - this.initVerifyByCertificatePEM = function(certPEM) { - throw "initVerifyByCertificatePEM(certPEM) not supported for this alg:prov=" + - this.algProvName; - }; - - /** - * Initialize this object for signing - * @name initSign - * @memberOf KJUR.crypto.Signature - * @function - * @param {Object} param RSAKey object of public key or associative array for ECDSA - * @deprecated from crypto 1.1.5. please use init() method instead. - * @description - * Private key information will be provided as 'param' parameter and the value will be - * following: - *
    - *
  • {@link RSAKey} object for RSA signing
  • - *
  • associative array for ECDSA signing - * (ex. {'ecprvhex': '1d3f..', 'eccurvename': 'secp256r1'})
  • - *
- * @example - * sig.initSign(prvKey) - */ - this.initSign = function(prvKey) { - throw "initSign(prvKey) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * Updates the data to be signed or verified by a string - * @name updateString - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} str string to use for the update - * @description - * @example - * sig.updateString('aaa') - */ - this.updateString = function(str) { - throw "updateString(str) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * Updates the data to be signed or verified by a hexadecimal string - * @name updateHex - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} hex hexadecimal string to use for the update - * @description - * @example - * sig.updateHex('1f2f3f') - */ - this.updateHex = function(hex) { - throw "updateHex(hex) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * Returns the signature bytes of all data updates as a hexadecimal string - * @name sign - * @memberOf KJUR.crypto.Signature - * @function - * @return the signature bytes as a hexadecimal string - * @description - * @example - * var hSigValue = sig.sign() - */ - this.sign = function() { - throw "sign() not supported for this alg:prov=" + this.algProvName; - }; - - /** - * performs final update on the sign using string, then returns the signature bytes of all data updates as a hexadecimal string - * @name signString - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} str string to final update - * @return the signature bytes of a hexadecimal string - * @description - * @example - * var hSigValue = sig.signString('aaa') - */ - this.signString = function(str) { - throw "digestString(str) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * performs final update on the sign using hexadecimal string, then returns the signature bytes of all data updates as a hexadecimal string - * @name signHex - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} hex hexadecimal string to final update - * @return the signature bytes of a hexadecimal string - * @description - * @example - * var hSigValue = sig.signHex('1fdc33') - */ - this.signHex = function(hex) { - throw "digestHex(hex) not supported for this alg:prov=" + this.algProvName; - }; - - /** - * verifies the passed-in signature. - * @name verify - * @memberOf KJUR.crypto.Signature - * @function - * @param {String} str string to final update - * @return {Boolean} true if the signature was verified, otherwise false - * @description - * @example - * var isValid = sig.verify('1fbcefdca4823a7(snip)') - */ - this.verify = function(hSigVal) { - throw "verify(hSigVal) not supported for this alg:prov=" + this.algProvName; - }; - - this.initParams = params; - - if (params !== undefined) { - if (params['alg'] !== undefined) { - this.algName = params['alg']; - if (params['prov'] === undefined) { - this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; - } else { - this.provName = params['prov']; - } - this.algProvName = this.algName + ":" + this.provName; - this.setAlgAndProvider(this.algName, this.provName); - this._setAlgNames(); - } - - if (params['psssaltlen'] !== undefined) this.pssSaltLen = params['psssaltlen']; - - if (params['prvkeypem'] !== undefined) { - if (params['prvkeypas'] !== undefined) { - throw "both prvkeypem and prvkeypas parameters not supported"; - } else { - try { - var prvKey = new RSAKey(); - prvKey.readPrivateKeyFromPEMString(params['prvkeypem']); - this.initSign(prvKey); - } catch (ex) { - throw "fatal error to load pem private key: " + ex; - } - } - } - } -}; - -/** - * static object for cryptographic function utilities - * @name KJUR.crypto.OID - * @class static object for cryptography related OIDs - * @property {Array} oidhex2name key value of hexadecimal OID and its name - * (ex. '2a8648ce3d030107' and 'secp256r1') - * @since crypto 1.1.3 - * @description - */ - - -KJUR.crypto.OID = new function() { - this.oidhex2name = { - '2a864886f70d010101': 'rsaEncryption', - '2a8648ce3d0201': 'ecPublicKey', - '2a8648ce380401': 'dsa', - '2a8648ce3d030107': 'secp256r1', - '2b8104001f': 'secp192k1', - '2b81040021': 'secp224r1', - '2b8104000a': 'secp256k1', - '2b81040023': 'secp521r1', - '2b81040022': 'secp384r1', - '2a8648ce380403': 'SHA1withDSA', // 1.2.840.10040.4.3 - '608648016503040301': 'SHA224withDSA', // 2.16.840.1.101.3.4.3.1 - '608648016503040302': 'SHA256withDSA', // 2.16.840.1.101.3.4.3.2 - }; -}; - -/*! base64x-1.1.3 (c) 2012-2014 Kenji Urushima | kjur.github.com/jsjws/license - */ -/* - * base64x.js - Base64url and supplementary functions for Tom Wu's base64.js library - * - * version: 1.1.3 (2014 May 25) - * - * Copyright (c) 2012-2014 Kenji Urushima (kenji.urushima@gmail.com) - * - * This software is licensed under the terms of the MIT License. - * http://kjur.github.com/jsjws/license/ - * - * The above copyright and license notice shall be - * included in all copies or substantial portions of the Software. - * - * DEPENDS ON: - * - base64.js - Tom Wu's Base64 library - */ - -/** - * Base64URL and supplementary functions for Tom Wu's base64.js library.
- * This class is just provide information about global functions - * defined in 'base64x.js'. The 'base64x.js' script file provides - * global functions for converting following data each other. - *
    - *
  • (ASCII) String
  • - *
  • UTF8 String including CJK, Latin and other characters
  • - *
  • byte array
  • - *
  • hexadecimal encoded String
  • - *
  • Full URIComponent encoded String (such like "%69%94")
  • - *
  • Base64 encoded String
  • - *
  • Base64URL encoded String
  • - *
- * All functions in 'base64x.js' are defined in {@link _global_} and not - * in this class. - * - * @class Base64URL and supplementary functions for Tom Wu's base64.js library - * @author Kenji Urushima - * @version 1.1 (07 May 2012) - * @requires base64.js - * @see 'jwjws'(JWS JavaScript Library) home page http://kjur.github.com/jsjws/ - * @see 'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/ - */ -function Base64x() { -} - -// ==== string / byte array ================================ -/** - * convert a string to an array of character codes - * @param {String} s - * @return {Array of Numbers} - */ -function stoBA(s) { - var a = new Array(); - for (var i = 0; i < s.length; i++) { - a[i] = s.charCodeAt(i); - } - return a; -} - -/** - * convert an array of character codes to a string - * @param {Array of Numbers} a array of character codes - * @return {String} s - */ -function BAtos(a) { - var s = ""; - for (var i = 0; i < a.length; i++) { - s = s + String.fromCharCode(a[i]); - } - return s; -} - -// ==== byte array / hex ================================ -/** - * convert an array of bytes(Number) to hexadecimal string.
- * @param {Array of Numbers} a array of bytes - * @return {String} hexadecimal string - */ -function BAtohex(a) { - var s = ""; - for (var i = 0; i < a.length; i++) { - var hex1 = a[i].toString(16); - if (hex1.length == 1) hex1 = "0" + hex1; - s = s + hex1; - } - return s; -} - -// ==== string / hex ================================ -/** - * convert a ASCII string to a hexadecimal string of ASCII codes.
- * NOTE: This can't be used for non ASCII characters. - * @param {s} s ASCII string - * @return {String} hexadecimal string - */ -function stohex(s) { - return BAtohex(stoBA(s)); -} - -// ==== string / base64 ================================ -/** - * convert a ASCII string to a Base64 encoded string.
- * NOTE: This can't be used for non ASCII characters. - * @param {s} s ASCII string - * @return {String} Base64 encoded string - */ -function stob64(s) { - return hex2b64(stohex(s)); -} - -// ==== string / base64url ================================ -/** - * convert a ASCII string to a Base64URL encoded string.
- * NOTE: This can't be used for non ASCII characters. - * @param {s} s ASCII string - * @return {String} Base64URL encoded string - */ -function stob64u(s) { - return b64tob64u(hex2b64(stohex(s))); -} - -/** - * convert a Base64URL encoded string to a ASCII string.
- * NOTE: This can't be used for Base64URL encoded non ASCII characters. - * @param {s} s Base64URL encoded string - * @return {String} ASCII string - */ -function b64utos(s) { - return BAtos(b64toBA(b64utob64(s))); -} - -// ==== base64 / base64url ================================ -/** - * convert a Base64 encoded string to a Base64URL encoded string.
- * Example: "ab+c3f/==" → "ab-c3f_" - * @param {String} s Base64 encoded string - * @return {String} Base64URL encoded string - */ -function b64tob64u(s) { - s = s.replace(/\=/g, ""); - s = s.replace(/\+/g, "-"); - s = s.replace(/\//g, "_"); - return s; -} - -/** - * convert a Base64URL encoded string to a Base64 encoded string.
- * Example: "ab-c3f_" → "ab+c3f/==" - * @param {String} s Base64URL encoded string - * @return {String} Base64 encoded string - */ -function b64utob64(s) { - if (s.length % 4 == 2) s = s + "=="; - else if (s.length % 4 == 3) s = s + "="; - s = s.replace(/-/g, "+"); - s = s.replace(/_/g, "/"); - return s; -} - -// ==== hex / base64url ================================ -/** - * convert a hexadecimal string to a Base64URL encoded string.
- * @param {String} s hexadecimal string - * @return {String} Base64URL encoded string - */ -function hextob64u(s) { - return b64tob64u(hex2b64(s)); -} - -/** - * convert a Base64URL encoded string to a hexadecimal string.
- * @param {String} s Base64URL encoded string - * @return {String} hexadecimal string - */ -function b64utohex(s) { - return b64tohex(b64utob64(s)); -} - -var utf8tob64u, b64utoutf8; - -if (typeof Buffer === 'function') -{ - utf8tob64u = function (s) - { - return b64tob64u(new Buffer(s, 'utf8').toString('base64')); - }; - - b64utoutf8 = function (s) - { - return new Buffer(b64utob64(s), 'base64').toString('utf8'); - }; -} -else -{ -// ==== utf8 / base64url ================================ -/** - * convert a UTF-8 encoded string including CJK or Latin to a Base64URL encoded string.
- * @param {String} s UTF-8 encoded string - * @return {String} Base64URL encoded string - * @since 1.1 - */ - utf8tob64u = function (s) - { - return hextob64u(uricmptohex(encodeURIComponentAll(s))); - }; - -/** - * convert a Base64URL encoded string to a UTF-8 encoded string including CJK or Latin.
- * @param {String} s Base64URL encoded string - * @return {String} UTF-8 encoded string - * @since 1.1 - */ - b64utoutf8 = function (s) - { - return decodeURIComponent(hextouricmp(b64utohex(s))); - }; -} - -// ==== utf8 / base64url ================================ -/** - * convert a UTF-8 encoded string including CJK or Latin to a Base64 encoded string.
- * @param {String} s UTF-8 encoded string - * @return {String} Base64 encoded string - * @since 1.1.1 - */ -function utf8tob64(s) { - return hex2b64(uricmptohex(encodeURIComponentAll(s))); -} - -/** - * convert a Base64 encoded string to a UTF-8 encoded string including CJK or Latin.
- * @param {String} s Base64 encoded string - * @return {String} UTF-8 encoded string - * @since 1.1.1 - */ -function b64toutf8(s) { - return decodeURIComponent(hextouricmp(b64tohex(s))); -} - -// ==== utf8 / hex ================================ -/** - * convert a UTF-8 encoded string including CJK or Latin to a hexadecimal encoded string.
- * @param {String} s UTF-8 encoded string - * @return {String} hexadecimal encoded string - * @since 1.1.1 - */ -function utf8tohex(s) { - return uricmptohex(encodeURIComponentAll(s)); -} - -/** - * convert a hexadecimal encoded string to a UTF-8 encoded string including CJK or Latin.
- * Note that when input is improper hexadecimal string as UTF-8 string, this function returns - * 'null'. - * @param {String} s hexadecimal encoded string - * @return {String} UTF-8 encoded string or null - * @since 1.1.1 - */ -function hextoutf8(s) { - return decodeURIComponent(hextouricmp(s)); -} - -/** - * convert a hexadecimal encoded string to raw string including non printable characters.
- * @param {String} s hexadecimal encoded string - * @return {String} raw string - * @since 1.1.2 - * @example - * hextorstr("610061") → "a\x00a" - */ -function hextorstr(sHex) { - var s = ""; - for (var i = 0; i < sHex.length - 1; i += 2) { - s += String.fromCharCode(parseInt(sHex.substr(i, 2), 16)); - } - return s; -} - -/** - * convert a raw string including non printable characters to hexadecimal encoded string.
- * @param {String} s raw string - * @return {String} hexadecimal encoded string - * @since 1.1.2 - * @example - * rstrtohex("a\x00a") → "610061" - */ -function rstrtohex(s) { - var result = ""; - for (var i = 0; i < s.length; i++) { - result += ("0" + s.charCodeAt(i).toString(16)).slice(-2); - } - return result; -} - -// ==== hex / b64nl ======================================= - -/* - * since base64x 1.1.3 - */ -function hextob64(s) { - return hex2b64(s); -} - -/* - * since base64x 1.1.3 - */ -function hextob64nl(s) { - var b64 = hextob64(s); - var b64nl = b64.replace(/(.{64})/g, "$1\r\n"); - b64nl = b64nl.replace(/\r\n$/, ''); - return b64nl; -} - -/* - * since base64x 1.1.3 - */ -function b64nltohex(s) { - var b64 = s.replace(/[^0-9A-Za-z\/+=]*/g, ''); - var hex = b64tohex(b64); - return hex; -} - -// ==== URIComponent / hex ================================ -/** - * convert a URLComponent string such like "%67%68" to a hexadecimal string.
- * @param {String} s URIComponent string such like "%67%68" - * @return {String} hexadecimal string - * @since 1.1 - */ -function uricmptohex(s) { - return s.replace(/%/g, ""); -} - -/** - * convert a hexadecimal string to a URLComponent string such like "%67%68".
- * @param {String} s hexadecimal string - * @return {String} URIComponent string such like "%67%68" - * @since 1.1 - */ -function hextouricmp(s) { - return s.replace(/(..)/g, "%$1"); -} - -// ==== URIComponent ================================ -/** - * convert UTFa hexadecimal string to a URLComponent string such like "%67%68".
- * Note that these "0-9A-Za-z!'()*-._~" characters will not - * converted to "%xx" format by builtin 'encodeURIComponent()' function. - * However this 'encodeURIComponentAll()' function will convert - * all of characters into "%xx" format. - * @param {String} s hexadecimal string - * @return {String} URIComponent string such like "%67%68" - * @since 1.1 - */ -function encodeURIComponentAll(u8) { - var s = encodeURIComponent(u8); - var s2 = ""; - for (var i = 0; i < s.length; i++) { - if (s[i] == "%") { - s2 = s2 + s.substr(i, 3); - i = i + 2; - } else { - s2 = s2 + "%" + stohex(s[i]); - } - } - return s2; -} - -// ==== new lines ================================ -/** - * convert all DOS new line("\r\n") to UNIX new line("\n") in - * a String "s". - * @param {String} s string - * @return {String} converted string - */ -function newline_toUnix(s) { - s = s.replace(/\r\n/mg, "\n"); - return s; -} - -/** - * convert all UNIX new line("\r\n") to DOS new line("\n") in - * a String "s". - * @param {String} s string - * @return {String} converted string - */ -function newline_toDos(s) { - s = s.replace(/\r\n/mg, "\n"); - s = s.replace(/\n/mg, "\r\n"); - return s; -} - -/*! Mike Samuel (c) 2009 | code.google.com/p/json-sans-eval - */ -// This source code is free for use in the public domain. -// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - -// http://code.google.com/p/json-sans-eval/ - -/** - * Parses a string of well-formed JSON text. - * - * If the input is not well-formed, then behavior is undefined, but it is - * deterministic and is guaranteed not to modify any object other than its - * return value. - * - * This does not use `eval` so is less likely to have obscure security bugs than - * json2.js. - * It is optimized for speed, so is much faster than json_parse.js. - * - * This library should be used whenever security is a concern (when JSON may - * come from an untrusted source), speed is a concern, and erroring on malformed - * JSON is *not* a concern. - * - * Pros Cons - * +-----------------------+-----------------------+ - * json_sans_eval.js | Fast, secure | Not validating | - * +-----------------------+-----------------------+ - * json_parse.js | Validating, secure | Slow | - * +-----------------------+-----------------------+ - * json2.js | Fast, some validation | Potentially insecure | - * +-----------------------+-----------------------+ - * - * json2.js is very fast, but potentially insecure since it calls `eval` to - * parse JSON data, so an attacker might be able to supply strange JS that - * looks like JSON, but that executes arbitrary javascript. - * If you do have to use json2.js with untrusted data, make sure you keep - * your version of json2.js up to date so that you get patches as they're - * released. - * - * @param {string} json per RFC 4627 - * @param {function (this:Object, string, *):*} opt_reviver optional function - * that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1. - * If supplied, the function is called with a string key, and a value. - * The value is the property of 'this'. The reviver should return - * the value to use in its place. So if dates were serialized as - * {@code { "type": "Date", "time": 1234 }}, then a reviver might look like - * {@code - * function (key, value) { - * if (value && typeof value === 'object' && 'Date' === value.type) { - * return new Date(value.time); - * } else { - * return value; - * } - * }}. - * If the reviver returns {@code undefined} then the property named by key - * will be deleted from its container. - * {@code this} is bound to the object containing the specified property. - * @return {Object|Array} - * @author Mike Samuel - */ -var jsonParse = (function () { - var number - = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)'; - var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]' - + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))'; - var string = '(?:\"' + oneChar + '*\")'; - - // Will match a value in a well-formed JSON file. - // If the input is not well-formed, may match strangely, but not in an unsafe - // way. - // Since this only matches value tokens, it does not match whitespace, colons, - // or commas. - var jsonToken = new RegExp( - '(?:false|true|null|[\\{\\}\\[\\]]' - + '|' + number - + '|' + string - + ')', 'g'); - - // Matches escape sequences in a string literal - var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g'); - - // Decodes escape sequences in object literals - var escapes = { - '"': '"', - '/': '/', - '\\': '\\', - 'b': '\b', - 'f': '\f', - 'n': '\n', - 'r': '\r', - 't': '\t' - }; - function unescapeOne(_, ch, hex) { - return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16)); - } - - // A non-falsy value that coerces to the empty string when used as a key. - var EMPTY_STRING = new String(''); - var SLASH = '\\'; - - // Constructor to use based on an open token. - var firstTokenCtors = { '{': Object, '[': Array }; - - var hop = Object.hasOwnProperty; - - return function (json, opt_reviver) { - // Split into tokens - var toks = json.match(jsonToken); - // Construct the object to return - var result; - var tok = toks[0]; - var topLevelPrimitive = false; - if ('{' === tok) { - result = {}; - } else if ('[' === tok) { - result = []; - } else { - // The RFC only allows arrays or objects at the top level, but the JSON.parse - // defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null - // at the top level. - result = []; - topLevelPrimitive = true; - } - - // If undefined, the key in an object key/value record to use for the next - // value parsed. - var key; - // Loop over remaining tokens maintaining a stack of uncompleted objects and - // arrays. - var stack = [result]; - for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) { - tok = toks[i]; - - var cont; - switch (tok.charCodeAt(0)) { - default: // sign or digit - cont = stack[0]; - cont[key || cont.length] = +(tok); - key = void 0; - break; - case 0x22: // '"' - tok = tok.substring(1, tok.length - 1); - if (tok.indexOf(SLASH) !== -1) { - tok = tok.replace(escapeSequence, unescapeOne); - } - cont = stack[0]; - if (!key) { - if (cont instanceof Array) { - key = cont.length; - } else { - key = tok || EMPTY_STRING; // Use as key for next value seen. - break; - } - } - cont[key] = tok; - key = void 0; - break; - case 0x5b: // '[' - cont = stack[0]; - stack.unshift(cont[key || cont.length] = []); - key = void 0; - break; - case 0x5d: // ']' - stack.shift(); - break; - case 0x66: // 'f' - cont = stack[0]; - cont[key || cont.length] = false; - key = void 0; - break; - case 0x6e: // 'n' - cont = stack[0]; - cont[key || cont.length] = null; - key = void 0; - break; - case 0x74: // 't' - cont = stack[0]; - cont[key || cont.length] = true; - key = void 0; - break; - case 0x7b: // '{' - cont = stack[0]; - stack.unshift(cont[key || cont.length] = {}); - key = void 0; - break; - case 0x7d: // '}' - stack.shift(); - break; - } - } - // Fail if we've got an uncompleted object. - if (topLevelPrimitive) { - if (stack.length !== 1) { throw new Error(); } - result = result[0]; - } else { - if (stack.length) { throw new Error(); } - } - - if (opt_reviver) { - // Based on walk as implemented in http://www.json.org/json2.js - var walk = function (holder, key) { - var value = holder[key]; - if (value && typeof value === 'object') { - var toDelete = null; - for (var k in value) { - if (hop.call(value, k) && value !== holder) { - // Recurse to properties first. This has the effect of causing - // the reviver to be called on the object graph depth-first. - - // Since 'this' is bound to the holder of the property, the - // reviver can access sibling properties of k including ones - // that have not yet been revived. - - // The value returned by the reviver is used in place of the - // current value of property k. - // If it returns undefined then the property is deleted. - var v = walk(value, k); - if (v !== void 0) { - value[k] = v; - } else { - // Deleting properties inside the loop has vaguely defined - // semantics in ES3 and ES3.1. - if (!toDelete) { toDelete = []; } - toDelete.push(k); - } - } - } - if (toDelete) { - for (var i = toDelete.length; --i >= 0;) { - delete value[toDelete[i]]; - } - } - } - return opt_reviver.call(holder, key, value); - }; - result = walk({ '': result }, ''); - } - - return result; - }; -})(); - -/*! jws-3.0.2 (c) 2013 Kenji Urushima | kjur.github.com/jsjws/license - */ -/* - * jws.js - JSON Web Signature Class - * - * version: 3.0.2 (2013 Sep 24) - * - * Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com) - * - * This software is licensed under the terms of the MIT License. - * http://kjur.github.com/jsjws/license/ - * - * The above copyright and license notice shall be - * included in all copies or substantial portions of the Software. - */ - -/** - * @fileOverview - * @name jws-3.0.js - * @author Kenji Urushima kenji.urushima@gmail.com - * @version 3.0.1 (2013-Sep-24) - * @since jsjws 1.0 - * @license MIT License - */ - -if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; -if (typeof KJUR.jws == "undefined" || !KJUR.jws) KJUR.jws = {}; - -/** - * JSON Web Signature(JWS) class.
- * @name KJUR.jws.JWS - * @class JSON Web Signature(JWS) class - * @property {Dictionary} parsedJWS This property is set after JWS signature verification.
- * Following "parsedJWS_*" properties can be accessed as "parsedJWS.*" because of - * JsDoc restriction. - * @property {String} parsedJWS_headB64U string of Encrypted JWS Header - * @property {String} parsedJWS_payloadB64U string of Encrypted JWS Payload - * @property {String} parsedJWS_sigvalB64U string of Encrypted JWS signature value - * @property {String} parsedJWS_si string of Signature Input - * @property {String} parsedJWS_sigvalH hexadecimal string of JWS signature value - * @property {String} parsedJWS_sigvalBI BigInteger(defined in jsbn.js) object of JWS signature value - * @property {String} parsedJWS_headS string of decoded JWS Header - * @property {String} parsedJWS_headS string of decoded JWS Payload - * @requires base64x.js, json-sans-eval.js and jsrsasign library - * @see 'jwjws'(JWS JavaScript Library) home page http://kjur.github.com/jsjws/ - * @see 'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/ - * @see IETF I-D JSON Web Algorithms (JWA) - * @since jsjws 1.0 - * @description - *

Supported Algorithms

- * Here is supported algorithm names for {@link KJUR.jws.JWS.sign} and {@link KJUR.jws.JWS.verify} - * methods. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
alg valuespec requirementjsjws support
HS256REQUIREDSUPPORTED
HS384OPTIONALSUPPORTED
HS512OPTIONALSUPPORTED
RS256RECOMMENDEDSUPPORTED
RS384OPTIONALSUPPORTED
RS512OPTIONALSUPPORTED
ES256RECOMMENDED+SUPPORTED
ES384OPTIONALSUPPORTED
ES512OPTIONAL-
PS256OPTIONALSUPPORTED
PS384OPTIONALSUPPORTED
PS512OPTIONALSUPPORTED
noneREQUIREDSUPPORTED
- * NOTE: HS384 is supported since jsjws 3.0.2 with jsrsasign 4.1.4. - */ -KJUR.jws.JWS = function() { - - // === utility ============================================================= - - /** - * parse JWS string and set public property 'parsedJWS' dictionary.
- * @name parseJWS - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sJWS JWS signature string to be parsed. - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - * @throws if JWS Header is a malformed JSON string. - * @since jws 1.1 - */ - this.parseJWS = function(sJWS, sigValNotNeeded) { - if ((this.parsedJWS !== undefined) && - (sigValNotNeeded || (this.parsedJWS.sigvalH !== undefined))) { - return; - } - if (sJWS.match(/^([^.]+)\.([^.]+)\.([^.]+)$/) == null) { - throw "JWS signature is not a form of 'Head.Payload.SigValue'."; - } - var b6Head = RegExp.$1; - var b6Payload = RegExp.$2; - var b6SigVal = RegExp.$3; - var sSI = b6Head + "." + b6Payload; - this.parsedJWS = {}; - this.parsedJWS.headB64U = b6Head; - this.parsedJWS.payloadB64U = b6Payload; - this.parsedJWS.sigvalB64U = b6SigVal; - this.parsedJWS.si = sSI; - - if (!sigValNotNeeded) { - var hSigVal = b64utohex(b6SigVal); - var biSigVal = parseBigInt(hSigVal, 16); - this.parsedJWS.sigvalH = hSigVal; - this.parsedJWS.sigvalBI = biSigVal; - } - - var sHead = b64utoutf8(b6Head); - var sPayload = b64utoutf8(b6Payload); - this.parsedJWS.headS = sHead; - this.parsedJWS.payloadS = sPayload; - - if (!KJUR.jws.JWS.isSafeJSONString(sHead, this.parsedJWS, 'headP')) - throw "malformed JSON string for JWS Head: " + sHead; - }; - - // ==== JWS Validation ========================================================= - function _getSignatureInputByString(sHead, sPayload) { - return utf8tob64u(sHead) + "." + utf8tob64u(sPayload); - }; - - function _getHashBySignatureInput(sSignatureInput, sHashAlg) { - var hashfunc = function(s) { return KJUR.crypto.Util.hashString(s, sHashAlg); }; - if (hashfunc == null) throw "hash function not defined in jsrsasign: " + sHashAlg; - return hashfunc(sSignatureInput); - }; - - function _jws_verifySignature(sHead, sPayload, hSig, hN, hE) { - var sSignatureInput = _getSignatureInputByString(sHead, sPayload); - var biSig = parseBigInt(hSig, 16); - return _rsasign_verifySignatureWithArgs(sSignatureInput, biSig, hN, hE); - }; - - /** - * verify JWS signature with naked RSA public key.
- * This only supports "RS256" and "RS512" algorithm. - * @name verifyJWSByNE - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sJWS JWS signature string to be verified - * @param {String} hN hexadecimal string for modulus of RSA public key - * @param {String} hE hexadecimal string for public exponent of RSA public key - * @return {String} returns 1 when JWS signature is valid, otherwise returns 0 - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - * @throws if JWS Header is a malformed JSON string. - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} - */ - this.verifyJWSByNE = function(sJWS, hN, hE) { - this.parseJWS(sJWS); - return _rsasign_verifySignatureWithArgs(this.parsedJWS.si, this.parsedJWS.sigvalBI, hN, hE); - }; - - /** - * verify JWS signature with RSA public key.
- * This only supports "RS256", "RS512", "PS256" and "PS512" algorithms. - * @name verifyJWSByKey - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sJWS JWS signature string to be verified - * @param {RSAKey} key RSA public key - * @return {Boolean} returns true when JWS signature is valid, otherwise returns false - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - * @throws if JWS Header is a malformed JSON string. - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} - */ - this.verifyJWSByKey = function(sJWS, key) { - this.parseJWS(sJWS); - var hashAlg = _jws_getHashAlgFromParsedHead(this.parsedJWS.headP); - var isPSS = this.parsedJWS.headP['alg'].substr(0, 2) == "PS"; - - if (key.hashAndVerify) { - return key.hashAndVerify(hashAlg, - new Buffer(this.parsedJWS.si, 'utf8').toString('base64'), - b64utob64(this.parsedJWS.sigvalB64U), - 'base64', - isPSS); - } else if (isPSS) { - return key.verifyStringPSS(this.parsedJWS.si, - this.parsedJWS.sigvalH, hashAlg); - } else { - return key.verifyString(this.parsedJWS.si, - this.parsedJWS.sigvalH); - } - }; - - /** - * verify JWS signature by PEM formatted X.509 certificate.
- * This only supports "RS256" and "RS512" algorithm. - * @name verifyJWSByPemX509Cert - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sJWS JWS signature string to be verified - * @param {String} sPemX509Cert string of PEM formatted X.509 certificate - * @return {String} returns 1 when JWS signature is valid, otherwise returns 0 - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - * @throws if JWS Header is a malformed JSON string. - * @since 1.1 - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} - */ - this.verifyJWSByPemX509Cert = function(sJWS, sPemX509Cert) { - this.parseJWS(sJWS); - var x509 = new X509(); - x509.readCertPEM(sPemX509Cert); - return x509.subjectPublicKeyRSA.verifyString(this.parsedJWS.si, this.parsedJWS.sigvalH); - }; - - // ==== JWS Generation ========================================================= - function _jws_getHashAlgFromParsedHead(head) { - var sigAlg = head["alg"]; - var hashAlg = ""; - - if (sigAlg != "RS256" && sigAlg != "RS512" && - sigAlg != "PS256" && sigAlg != "PS512") - throw "JWS signature algorithm not supported: " + sigAlg; - if (sigAlg.substr(2) == "256") hashAlg = "sha256"; - if (sigAlg.substr(2) == "512") hashAlg = "sha512"; - return hashAlg; - }; - - function _jws_getHashAlgFromHead(sHead) { - return _jws_getHashAlgFromParsedHead(jsonParse(sHead)); - }; - - function _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD) { - var rsa = new RSAKey(); - rsa.setPrivate(hN, hE, hD); - - var hashAlg = _jws_getHashAlgFromHead(sHead); - var sigValue = rsa.signString(sSI, hashAlg); - return sigValue; - }; - - function _jws_generateSignatureValueBySI_Key(sHead, sPayload, sSI, key, head) { - var hashAlg = null; - if (typeof head == "undefined") { - hashAlg = _jws_getHashAlgFromHead(sHead); - } else { - hashAlg = _jws_getHashAlgFromParsedHead(head); - } - - var isPSS = head['alg'].substr(0, 2) == "PS"; - - if (key.hashAndSign) { - return b64tob64u(key.hashAndSign(hashAlg, sSI, 'binary', 'base64', isPSS)); - } else if (isPSS) { - return hextob64u(key.signStringPSS(sSI, hashAlg)); - } else { - return hextob64u(key.signString(sSI, hashAlg)); - } - }; - - function _jws_generateSignatureValueByNED(sHead, sPayload, hN, hE, hD) { - var sSI = _getSignatureInputByString(sHead, sPayload); - return _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD); - }; - - /** - * generate JWS signature by Header, Payload and a naked RSA private key.
- * This only supports "RS256" and "RS512" algorithm. - * @name generateJWSByNED - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sHead string of JWS Header - * @param {String} sPayload string of JWS Payload - * @param {String} hN hexadecimal string for modulus of RSA public key - * @param {String} hE hexadecimal string for public exponent of RSA public key - * @param {String} hD hexadecimal string for private exponent of RSA private key - * @return {String} JWS signature string - * @throws if sHead is a malformed JSON string. - * @throws if supported signature algorithm was not specified in JSON Header. - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} - */ - this.generateJWSByNED = function(sHead, sPayload, hN, hE, hD) { - if (!KJUR.jws.JWS.isSafeJSONString(sHead)) throw "JWS Head is not safe JSON string: " + sHead; - var sSI = _getSignatureInputByString(sHead, sPayload); - var hSigValue = _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD); - var b64SigValue = hextob64u(hSigValue); - - this.parsedJWS = {}; - this.parsedJWS.headB64U = sSI.split(".")[0]; - this.parsedJWS.payloadB64U = sSI.split(".")[1]; - this.parsedJWS.sigvalB64U = b64SigValue; - - return sSI + "." + b64SigValue; - }; - - /** - * generate JWS signature by Header, Payload and a RSA private key.
- * This only supports "RS256", "RS512", "PS256" and "PS512" algorithms. - * @name generateJWSByKey - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sHead string of JWS Header - * @param {String} sPayload string of JWS Payload - * @param {RSAKey} RSA private key - * @return {String} JWS signature string - * @throws if sHead is a malformed JSON string. - * @throws if supported signature algorithm was not specified in JSON Header. - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} - */ - this.generateJWSByKey = function(sHead, sPayload, key) { - var obj = {}; - if (!KJUR.jws.JWS.isSafeJSONString(sHead, obj, 'headP')) - throw "JWS Head is not safe JSON string: " + sHead; - var sSI = _getSignatureInputByString(sHead, sPayload); - var b64SigValue = _jws_generateSignatureValueBySI_Key(sHead, sPayload, sSI, key, obj.headP); - - this.parsedJWS = {}; - this.parsedJWS.headB64U = sSI.split(".")[0]; - this.parsedJWS.payloadB64U = sSI.split(".")[1]; - this.parsedJWS.sigvalB64U = b64SigValue; - - return sSI + "." + b64SigValue; - }; - - // === sign with PKCS#1 RSA private key ===================================================== - function _jws_generateSignatureValueBySI_PemPrvKey(sHead, sPayload, sSI, sPemPrvKey) { - var rsa = new RSAKey(); - rsa.readPrivateKeyFromPEMString(sPemPrvKey); - var hashAlg = _jws_getHashAlgFromHead(sHead); - var sigValue = rsa.signString(sSI, hashAlg); - return sigValue; - }; - - /** - * generate JWS signature by Header, Payload and a PEM formatted PKCS#1 RSA private key.
- * This only supports "RS256" and "RS512" algorithm. - * @name generateJWSByP1PrvKey - * @memberOf KJUR.jws.JWS - * @function - * @param {String} sHead string of JWS Header - * @param {String} sPayload string of JWS Payload - * @param {String} string for sPemPrvKey PEM formatted PKCS#1 RSA private key
- * Heading and trailing space characters in PEM key will be ignored. - * @return {String} JWS signature string - * @throws if sHead is a malformed JSON string. - * @throws if supported signature algorithm was not specified in JSON Header. - * @since 1.1 - * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} - */ - this.generateJWSByP1PrvKey = function(sHead, sPayload, sPemPrvKey) { - if (!KJUR.jws.JWS.isSafeJSONString(sHead)) throw "JWS Head is not safe JSON string: " + sHead; - var sSI = _getSignatureInputByString(sHead, sPayload); - var hSigValue = _jws_generateSignatureValueBySI_PemPrvKey(sHead, sPayload, sSI, sPemPrvKey); - var b64SigValue = hextob64u(hSigValue); - - this.parsedJWS = {}; - this.parsedJWS.headB64U = sSI.split(".")[0]; - this.parsedJWS.payloadB64U = sSI.split(".")[1]; - this.parsedJWS.sigvalB64U = b64SigValue; - - return sSI + "." + b64SigValue; - }; -}; - -// === major static method ======================================================== - -/** - * generate JWS signature by specified key
- * @name sign - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} alg JWS algorithm name to sign and force set to sHead or null - * @param {String} sHead string of JWS Header - * @param {String} sPayload string of JWS Payload - * @param {String} key string of private key or key object to sign - * @param {String} pass (OPTION)passcode to use encrypted private key - * @return {String} JWS signature string - * @since jws 3.0.0 - * @see jsrsasign KJUR.crypto.Signature method - * @see jsrsasign KJUR.crypto.Mac method - * @description - * This method supports following algorithms. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
alg valuespec requirementjsjws support
HS256REQUIREDSUPPORTED
HS384OPTIONAL-
HS512OPTIONALSUPPORTED
RS256RECOMMENDEDSUPPORTED
RS384OPTIONALSUPPORTED
RS512OPTIONALSUPPORTED
ES256RECOMMENDED+SUPPORTED
ES384OPTIONALSUPPORTED
ES512OPTIONAL-
PS256OPTIONALSUPPORTED
PS384OPTIONALSUPPORTED
PS512OPTIONALSUPPORTED
noneREQUIREDSUPPORTED
- *
- *
NOTE1: - *
salt length of RSAPSS signature is the same as the hash algorithm length - * because of IETF JOSE ML discussion. - *
NOTE2: - *
The reason of HS384 unsupport is - * CryptoJS HmacSHA384 bug. - *
- */ -KJUR.jws.JWS.sign = function(alg, sHeader, sPayload, key, pass) { - var ns1 = KJUR.jws.JWS; - - if (! ns1.isSafeJSONString(sHeader)) - throw "JWS Head is not safe JSON string: " + sHead; - - var pHeader = ns1.readSafeJSONString(sHeader); - - // 1. use alg if defined in sHeader - if ((alg == '' || alg == null) && - pHeader['alg'] !== undefined) { - alg = pHeader['alg']; - } - - // 2. set alg in sHeader if undefined - if ((alg != '' && alg != null) && - pHeader['alg'] === undefined) { - pHeader['alg'] = alg; - sHeader = JSON.stringify(pHeader); - } - - // 3. set signature algorithm like SHA1withRSA - var sigAlg = null; - if (ns1.jwsalg2sigalg[alg] === undefined) { - throw "unsupported alg name: " + alg; - } else { - sigAlg = ns1.jwsalg2sigalg[alg]; - } - - var uHeader = utf8tob64u(sHeader); - var uPayload = utf8tob64u(sPayload); - var uSignatureInput = uHeader + "." + uPayload - - // 4. sign - var hSig = ""; - if (sigAlg.substr(0, 4) == "Hmac") { - if (key === undefined) - throw "hexadecimal key shall be specified for HMAC"; - var mac = new KJUR.crypto.Mac({'alg': sigAlg, 'pass': hextorstr(key)}); - mac.updateString(uSignatureInput); - hSig = mac.doFinal(); - } else if (sigAlg.indexOf("withECDSA") != -1) { - var sig = new KJUR.crypto.Signature({'alg': sigAlg}); - sig.init(key, pass); - sig.updateString(uSignatureInput); - hASN1Sig = sig.sign(); - hSig = KJUR.crypto.ECDSA.asn1SigToConcatSig(hASN1Sig); - } else if (sigAlg != "none") { - var sig = new KJUR.crypto.Signature({'alg': sigAlg}); - sig.init(key, pass); - sig.updateString(uSignatureInput); - hSig = sig.sign(); - } - - var uSig = hextob64u(hSig); - return uSignatureInput + "." + uSig; -}; - -/** - * verify JWS signature by specified key or certificate
- * @name verify - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} sJWS string of JWS signature to verify - * @param {String} key string of public key, certificate or key object to verify - * @return {Boolean} true if the signature is valid otherwise false - * @since jws 3.0.0 - * @see jsrsasign KJUR.crypto.Signature method - * @see jsrsasign KJUR.crypto.Mac method - */ -KJUR.jws.JWS.verify = function(sJWS, key) { - var jws = KJUR.jws.JWS; - var a = sJWS.split("."); - var uHeader = a[0]; - var uPayload = a[1]; - var uSignatureInput = uHeader + "." + uPayload; - var hSig = b64utohex(a[2]); - - var pHeader = jws.readSafeJSONString(b64utoutf8(a[0])); - var alg = null; - if (pHeader.alg === undefined) { - throw "algorithm not specified in header"; - } else { - alg = pHeader.alg; - } - - var sigAlg = null; - if (jws.jwsalg2sigalg[pHeader.alg] === undefined) { - throw "unsupported alg name: " + alg; - } else { - sigAlg = jws.jwsalg2sigalg[alg]; - } - - // x. verify - if (sigAlg == "none") { - return true; - } else if (sigAlg.substr(0, 4) == "Hmac") { - if (key === undefined) - throw "hexadecimal key shall be specified for HMAC"; - var mac = new KJUR.crypto.Mac({'alg': sigAlg, 'pass': hextorstr(key)}); - mac.updateString(uSignatureInput); - hSig2 = mac.doFinal(); - return hSig == hSig2; - } else if (sigAlg.indexOf("withECDSA") != -1) { - var hASN1Sig = null; - try { - hASN1Sig = KJUR.crypto.ECDSA.concatSigToASN1Sig(hSig); - } catch (ex) { - return false; - } - var sig = new KJUR.crypto.Signature({'alg': sigAlg}); - sig.init(key) - sig.updateString(uSignatureInput); - return sig.verify(hASN1Sig); - } else { - var sig = new KJUR.crypto.Signature({'alg': sigAlg}); - sig.init(key) - sig.updateString(uSignatureInput); - return sig.verify(hSig); - } -}; - -/* - * @since jws 3.0.0 - */ -KJUR.jws.JWS.jwsalg2sigalg = { - "HS256": "HmacSHA256", - //"HS384": "HmacSHA384", // unsupported because of CryptoJS bug - "HS512": "HmacSHA512", - "RS256": "SHA256withRSA", - "RS384": "SHA384withRSA", - "RS512": "SHA512withRSA", - "ES256": "SHA256withECDSA", - "ES384": "SHA384withECDSA", - //"ES512": "SHA512withECDSA", // unsupported because of jsrsasign's bug - "PS256": "SHA256withRSAandMGF1", - "PS384": "SHA384withRSAandMGF1", - "PS512": "SHA512withRSAandMGF1", - "none": "none", -}; - -// === utility static method ====================================================== - -/** - * check whether a String "s" is a safe JSON string or not.
- * If a String "s" is a malformed JSON string or an other object type - * this returns 0, otherwise this returns 1. - * @name isSafeJSONString - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} s JSON string - * @return {Number} 1 or 0 - */ -KJUR.jws.JWS.isSafeJSONString = function(s, h, p) { - var o = null; - try { - o = jsonParse(s); - if (typeof o != "object") return 0; - if (o.constructor === Array) return 0; - if (h) h[p] = o; - return 1; - } catch (ex) { - return 0; - } -}; - -/** - * read a String "s" as JSON object if it is safe.
- * If a String "s" is a malformed JSON string or not JSON string, - * this returns null, otherwise returns JSON object. - * @name readSafeJSONString - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} s JSON string - * @return {Object} JSON object or null - * @since 1.1.1 - */ -KJUR.jws.JWS.readSafeJSONString = function(s) { - var o = null; - try { - o = jsonParse(s); - if (typeof o != "object") return null; - if (o.constructor === Array) return null; - return o; - } catch (ex) { - return null; - } -}; - -/** - * get Encoed Signature Value from JWS string.
- * @name getEncodedSignatureValueFromJWS - * @memberOf KJUR.jws.JWS - * @function - * @static - * @param {String} sJWS JWS signature string to be verified - * @return {String} string of Encoded Signature Value - * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". - */ -KJUR.jws.JWS.getEncodedSignatureValueFromJWS = function(sJWS) { - if (sJWS.match(/^[^.]+\.[^.]+\.([^.]+)$/) == null) { - throw "JWS signature is not a form of 'Head.Payload.SigValue'."; - } - return RegExp.$1; -}; - -/** - * IntDate class for time representation for JSON Web Token(JWT) - * @class KJUR.jws.IntDate class - * @name KJUR.jws.IntDate - * @since jws 3.0.1 - * @description - * Utility class for IntDate which is integer representation of UNIX origin time - * used in JSON Web Token(JWT). - */ -KJUR.jws.IntDate = function() { -}; - -/** - * @name get - * @memberOf KJUR.jws.IntDate - * @function - * @static - * @param {String} s string of time representation - * @return {Integer} UNIX origin time in seconds for argument 's' - * @since jws 3.0.1 - * @throws "unsupported format: s" when malformed format - * @description - * This method will accept following representation of time. - *
    - *
  • now - current time
  • - *
  • now + 1hour - after 1 hour from now
  • - *
  • now + 1day - after 1 day from now
  • - *
  • now + 1month - after 30 days from now
  • - *
  • now + 1year - after 365 days from now
  • - *
  • YYYYmmDDHHMMSSZ - UTC time (ex. 20130828235959Z)
  • - *
  • number - UNIX origin time (seconds from 1970-01-01 00:00:00) (ex. 1377714748)
  • - *
- */ -KJUR.jws.IntDate.get = function(s) { - if (s == "now") { - return KJUR.jws.IntDate.getNow(); - } else if (s == "now + 1hour") { - return KJUR.jws.IntDate.getNow() + 60 * 60; - } else if (s == "now + 1day") { - return KJUR.jws.IntDate.getNow() + 60 * 60 * 24; - } else if (s == "now + 1month") { - return KJUR.jws.IntDate.getNow() + 60 * 60 * 24 * 30; - } else if (s == "now + 1year") { - return KJUR.jws.IntDate.getNow() + 60 * 60 * 24 * 365; - } else if (s.match(/Z$/)) { - return KJUR.jws.IntDate.getZulu(s); - } else if (s.match(/^[0-9]+$/)) { - return parseInt(s); - } - throw "unsupported format: " + s; -}; - -KJUR.jws.IntDate.getZulu = function(s) { - if (a = s.match(/(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)Z/)) { - var year = parseInt(RegExp.$1); - var month = parseInt(RegExp.$2) - 1; - var day = parseInt(RegExp.$3); - var hour = parseInt(RegExp.$4); - var min = parseInt(RegExp.$5); - var sec = parseInt(RegExp.$6); - var d = new Date(Date.UTC(year, month, day, hour, min, sec)); - return ~~(d / 1000); - } - throw "unsupported format: " + s; -}; - -/* - * @since jws 3.0.1 - */ -KJUR.jws.IntDate.getNow = function() { - var d = ~~(new Date() / 1000); - return d; -}; - -/* - * @since jws 3.0.1 - */ -KJUR.jws.IntDate.intDate2UTCString = function(intDate) { - var d = new Date(intDate * 1000); - return d.toUTCString(); -}; - -/* - * @since jws 3.0.1 - */ -KJUR.jws.IntDate.intDate2Zulu = function(intDate) { - var d = new Date(intDate * 1000); - var year = ("0000" + d.getUTCFullYear()).slice(-4); - var mon = ("00" + (d.getUTCMonth() + 1)).slice(-2); - var day = ("00" + d.getUTCDate()).slice(-2); - var hour = ("00" + d.getUTCHours()).slice(-2); - var min = ("00" + d.getUTCMinutes()).slice(-2); - var sec = ("00" + d.getUTCSeconds()).slice(-2); - return year + mon + day + hour + min + sec + "Z"; -}; - -/*! - * @overview es6-promise - a tiny implementation of Promises/A+. - * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) - * @license Licensed under MIT license - * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE - * @version 3.0.2 - */ - -(function() { - "use strict"; - function lib$es6$promise$utils$$objectOrFunction(x) { - return typeof x === 'function' || (typeof x === 'object' && x !== null); - } - - function lib$es6$promise$utils$$isFunction(x) { - return typeof x === 'function'; - } - - function lib$es6$promise$utils$$isMaybeThenable(x) { - return typeof x === 'object' && x !== null; - } - - var lib$es6$promise$utils$$_isArray; - if (!Array.isArray) { - lib$es6$promise$utils$$_isArray = function (x) { - return Object.prototype.toString.call(x) === '[object Array]'; - }; - } else { - lib$es6$promise$utils$$_isArray = Array.isArray; - } - - var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray; - var lib$es6$promise$asap$$len = 0; - var lib$es6$promise$asap$$toString = {}.toString; - var lib$es6$promise$asap$$vertxNext; - var lib$es6$promise$asap$$customSchedulerFn; - - var lib$es6$promise$asap$$asap = function asap(callback, arg) { - lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback; - lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg; - lib$es6$promise$asap$$len += 2; - if (lib$es6$promise$asap$$len === 2) { - // If len is 2, that means that we need to schedule an async flush. - // If additional callbacks are queued before the queue is flushed, they - // will be processed by this flush that we are scheduling. - if (lib$es6$promise$asap$$customSchedulerFn) { - lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush); - } else { - lib$es6$promise$asap$$scheduleFlush(); - } - } - } - - function lib$es6$promise$asap$$setScheduler(scheduleFn) { - lib$es6$promise$asap$$customSchedulerFn = scheduleFn; - } - - function lib$es6$promise$asap$$setAsap(asapFn) { - lib$es6$promise$asap$$asap = asapFn; - } - - var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined; - var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {}; - var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver; - var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; - - // test for web worker but not in IE10 - var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && - typeof importScripts !== 'undefined' && - typeof MessageChannel !== 'undefined'; - - // node - function lib$es6$promise$asap$$useNextTick() { - // node version 0.10.x displays a deprecation warning when nextTick is used recursively - // see https://github.com/cujojs/when/issues/410 for details - return function() { - process.nextTick(lib$es6$promise$asap$$flush); - }; - } - - // vertx - function lib$es6$promise$asap$$useVertxTimer() { - return function() { - lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush); - }; - } - - function lib$es6$promise$asap$$useMutationObserver() { - var iterations = 0; - var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush); - var node = document.createTextNode(''); - observer.observe(node, { characterData: true }); - - return function() { - node.data = (iterations = ++iterations % 2); - }; - } - - // web worker - function lib$es6$promise$asap$$useMessageChannel() { - var channel = new MessageChannel(); - channel.port1.onmessage = lib$es6$promise$asap$$flush; - return function () { - channel.port2.postMessage(0); - }; - } - - function lib$es6$promise$asap$$useSetTimeout() { - return function() { - setTimeout(lib$es6$promise$asap$$flush, 1); - }; - } - - var lib$es6$promise$asap$$queue = new Array(1000); - function lib$es6$promise$asap$$flush() { - for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) { - var callback = lib$es6$promise$asap$$queue[i]; - var arg = lib$es6$promise$asap$$queue[i+1]; - - callback(arg); - - lib$es6$promise$asap$$queue[i] = undefined; - lib$es6$promise$asap$$queue[i+1] = undefined; - } - - lib$es6$promise$asap$$len = 0; - } - - function lib$es6$promise$asap$$attemptVertx() { - try { - var r = require; - var vertx = r('vertx'); - lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext; - return lib$es6$promise$asap$$useVertxTimer(); - } catch(e) { - return lib$es6$promise$asap$$useSetTimeout(); - } - } - - var lib$es6$promise$asap$$scheduleFlush; - // Decide what async method to use to triggering processing of queued callbacks: - if (lib$es6$promise$asap$$isNode) { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick(); - } else if (lib$es6$promise$asap$$BrowserMutationObserver) { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver(); - } else if (lib$es6$promise$asap$$isWorker) { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel(); - } else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertx(); - } else { - lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout(); - } - - function lib$es6$promise$$internal$$noop() {} - - var lib$es6$promise$$internal$$PENDING = void 0; - var lib$es6$promise$$internal$$FULFILLED = 1; - var lib$es6$promise$$internal$$REJECTED = 2; - - var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject(); - - function lib$es6$promise$$internal$$selfFulfillment() { - return new TypeError("You cannot resolve a promise with itself"); - } - - function lib$es6$promise$$internal$$cannotReturnOwn() { - return new TypeError('A promises callback cannot return that same promise.'); - } - - function lib$es6$promise$$internal$$getThen(promise) { - try { - return promise.then; - } catch(error) { - lib$es6$promise$$internal$$GET_THEN_ERROR.error = error; - return lib$es6$promise$$internal$$GET_THEN_ERROR; - } - } - - function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { - try { - then.call(value, fulfillmentHandler, rejectionHandler); - } catch(e) { - return e; - } - } - - function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) { - lib$es6$promise$asap$$asap(function(promise) { - var sealed = false; - var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) { - if (sealed) { return; } - sealed = true; - if (thenable !== value) { - lib$es6$promise$$internal$$resolve(promise, value); - } else { - lib$es6$promise$$internal$$fulfill(promise, value); - } - }, function(reason) { - if (sealed) { return; } - sealed = true; - - lib$es6$promise$$internal$$reject(promise, reason); - }, 'Settle: ' + (promise._label || ' unknown promise')); - - if (!sealed && error) { - sealed = true; - lib$es6$promise$$internal$$reject(promise, error); - } - }, promise); - } - - function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) { - if (thenable._state === lib$es6$promise$$internal$$FULFILLED) { - lib$es6$promise$$internal$$fulfill(promise, thenable._result); - } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) { - lib$es6$promise$$internal$$reject(promise, thenable._result); - } else { - lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) { - lib$es6$promise$$internal$$resolve(promise, value); - }, function(reason) { - lib$es6$promise$$internal$$reject(promise, reason); - }); - } - } - - function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) { - if (maybeThenable.constructor === promise.constructor) { - lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable); - } else { - var then = lib$es6$promise$$internal$$getThen(maybeThenable); - - if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) { - lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error); - } else if (then === undefined) { - lib$es6$promise$$internal$$fulfill(promise, maybeThenable); - } else if (lib$es6$promise$utils$$isFunction(then)) { - lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then); - } else { - lib$es6$promise$$internal$$fulfill(promise, maybeThenable); - } - } - } - - function lib$es6$promise$$internal$$resolve(promise, value) { - if (promise === value) { - lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFulfillment()); - } else if (lib$es6$promise$utils$$objectOrFunction(value)) { - lib$es6$promise$$internal$$handleMaybeThenable(promise, value); - } else { - lib$es6$promise$$internal$$fulfill(promise, value); - } - } - - function lib$es6$promise$$internal$$publishRejection(promise) { - if (promise._onerror) { - promise._onerror(promise._result); - } - - lib$es6$promise$$internal$$publish(promise); - } - - function lib$es6$promise$$internal$$fulfill(promise, value) { - if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } - - promise._result = value; - promise._state = lib$es6$promise$$internal$$FULFILLED; - - if (promise._subscribers.length !== 0) { - lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise); - } - } - - function lib$es6$promise$$internal$$reject(promise, reason) { - if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } - promise._state = lib$es6$promise$$internal$$REJECTED; - promise._result = reason; - - lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise); - } - - function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) { - var subscribers = parent._subscribers; - var length = subscribers.length; - - parent._onerror = null; - - subscribers[length] = child; - subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment; - subscribers[length + lib$es6$promise$$internal$$REJECTED] = onRejection; - - if (length === 0 && parent._state) { - lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent); - } - } - - function lib$es6$promise$$internal$$publish(promise) { - var subscribers = promise._subscribers; - var settled = promise._state; - - if (subscribers.length === 0) { return; } - - var child, callback, detail = promise._result; - - for (var i = 0; i < subscribers.length; i += 3) { - child = subscribers[i]; - callback = subscribers[i + settled]; - - if (child) { - lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail); - } else { - callback(detail); - } - } - - promise._subscribers.length = 0; - } - - function lib$es6$promise$$internal$$ErrorObject() { - this.error = null; - } - - var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject(); - - function lib$es6$promise$$internal$$tryCatch(callback, detail) { - try { - return callback(detail); - } catch(e) { - lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e; - return lib$es6$promise$$internal$$TRY_CATCH_ERROR; - } - } - - function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) { - var hasCallback = lib$es6$promise$utils$$isFunction(callback), - value, error, succeeded, failed; - - if (hasCallback) { - value = lib$es6$promise$$internal$$tryCatch(callback, detail); - - if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) { - failed = true; - error = value.error; - value = null; - } else { - succeeded = true; - } - - if (promise === value) { - lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn()); - return; - } - - } else { - value = detail; - succeeded = true; - } - - if (promise._state !== lib$es6$promise$$internal$$PENDING) { - // noop - } else if (hasCallback && succeeded) { - lib$es6$promise$$internal$$resolve(promise, value); - } else if (failed) { - lib$es6$promise$$internal$$reject(promise, error); - } else if (settled === lib$es6$promise$$internal$$FULFILLED) { - lib$es6$promise$$internal$$fulfill(promise, value); - } else if (settled === lib$es6$promise$$internal$$REJECTED) { - lib$es6$promise$$internal$$reject(promise, value); - } - } - - function lib$es6$promise$$internal$$initializePromise(promise, resolver) { - try { - resolver(function resolvePromise(value){ - lib$es6$promise$$internal$$resolve(promise, value); - }, function rejectPromise(reason) { - lib$es6$promise$$internal$$reject(promise, reason); - }); - } catch(e) { - lib$es6$promise$$internal$$reject(promise, e); - } - } - - function lib$es6$promise$enumerator$$Enumerator(Constructor, input) { - var enumerator = this; - - enumerator._instanceConstructor = Constructor; - enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop); - - if (enumerator._validateInput(input)) { - enumerator._input = input; - enumerator.length = input.length; - enumerator._remaining = input.length; - - enumerator._init(); - - if (enumerator.length === 0) { - lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); - } else { - enumerator.length = enumerator.length || 0; - enumerator._enumerate(); - if (enumerator._remaining === 0) { - lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); - } - } - } else { - lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError()); - } - } - - lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) { - return lib$es6$promise$utils$$isArray(input); - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() { - return new Error('Array Methods must be provided an Array'); - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._init = function() { - this._result = new Array(this.length); - }; - - var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator; - - lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() { - var enumerator = this; - - var length = enumerator.length; - var promise = enumerator.promise; - var input = enumerator._input; - - for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { - enumerator._eachEntry(input[i], i); - } - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { - var enumerator = this; - var c = enumerator._instanceConstructor; - - if (lib$es6$promise$utils$$isMaybeThenable(entry)) { - if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) { - entry._onerror = null; - enumerator._settledAt(entry._state, i, entry._result); - } else { - enumerator._willSettleAt(c.resolve(entry), i); - } - } else { - enumerator._remaining--; - enumerator._result[i] = entry; - } - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { - var enumerator = this; - var promise = enumerator.promise; - - if (promise._state === lib$es6$promise$$internal$$PENDING) { - enumerator._remaining--; - - if (state === lib$es6$promise$$internal$$REJECTED) { - lib$es6$promise$$internal$$reject(promise, value); - } else { - enumerator._result[i] = value; - } - } - - if (enumerator._remaining === 0) { - lib$es6$promise$$internal$$fulfill(promise, enumerator._result); - } - }; - - lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { - var enumerator = this; - - lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) { - enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value); - }, function(reason) { - enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason); - }); - }; - function lib$es6$promise$promise$all$$all(entries) { - return new lib$es6$promise$enumerator$$default(this, entries).promise; - } - var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all; - function lib$es6$promise$promise$race$$race(entries) { - /*jshint validthis:true */ - var Constructor = this; - - var promise = new Constructor(lib$es6$promise$$internal$$noop); - - if (!lib$es6$promise$utils$$isArray(entries)) { - lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.')); - return promise; - } - - var length = entries.length; - - function onFulfillment(value) { - lib$es6$promise$$internal$$resolve(promise, value); - } - - function onRejection(reason) { - lib$es6$promise$$internal$$reject(promise, reason); - } - - for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { - lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); - } - - return promise; - } - var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race; - function lib$es6$promise$promise$resolve$$resolve(object) { - /*jshint validthis:true */ - var Constructor = this; - - if (object && typeof object === 'object' && object.constructor === Constructor) { - return object; - } - - var promise = new Constructor(lib$es6$promise$$internal$$noop); - lib$es6$promise$$internal$$resolve(promise, object); - return promise; - } - var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve; - function lib$es6$promise$promise$reject$$reject(reason) { - /*jshint validthis:true */ - var Constructor = this; - var promise = new Constructor(lib$es6$promise$$internal$$noop); - lib$es6$promise$$internal$$reject(promise, reason); - return promise; - } - var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject; - - var lib$es6$promise$promise$$counter = 0; - - function lib$es6$promise$promise$$needsResolver() { - throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); - } - - function lib$es6$promise$promise$$needsNew() { - throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); - } - - var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise; - /** - Promise objects represent the eventual result of an asynchronous operation. The - primary way of interacting with a promise is through its `then` method, which - registers callbacks to receive either a promise's eventual value or the reason - why the promise cannot be fulfilled. - - Terminology - ----------- - - - `promise` is an object or function with a `then` method whose behavior conforms to this specification. - - `thenable` is an object or function that defines a `then` method. - - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). - - `exception` is a value that is thrown using the throw statement. - - `reason` is a value that indicates why a promise was rejected. - - `settled` the final resting state of a promise, fulfilled or rejected. - - A promise can be in one of three states: pending, fulfilled, or rejected. - - Promises that are fulfilled have a fulfillment value and are in the fulfilled - state. Promises that are rejected have a rejection reason and are in the - rejected state. A fulfillment value is never a thenable. - - Promises can also be said to *resolve* a value. If this value is also a - promise, then the original promise's settled state will match the value's - settled state. So a promise that *resolves* a promise that rejects will - itself reject, and a promise that *resolves* a promise that fulfills will - itself fulfill. - - - Basic Usage: - ------------ - - ```js - var promise = new Promise(function(resolve, reject) { - // on success - resolve(value); - - // on failure - reject(reason); - }); - - promise.then(function(value) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Advanced Usage: - --------------- - - Promises shine when abstracting away asynchronous interactions such as - `XMLHttpRequest`s. - - ```js - function getJSON(url) { - return new Promise(function(resolve, reject){ - var xhr = new XMLHttpRequest(); - - xhr.open('GET', url); - xhr.onreadystatechange = handler; - xhr.responseType = 'json'; - xhr.setRequestHeader('Accept', 'application/json'); - xhr.send(); - - function handler() { - if (this.readyState === this.DONE) { - if (this.status === 200) { - resolve(this.response); - } else { - reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); - } - } - }; - }); - } - - getJSON('/posts.json').then(function(json) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Unlike callbacks, promises are great composable primitives. - - ```js - Promise.all([ - getJSON('/posts'), - getJSON('/comments') - ]).then(function(values){ - values[0] // => postsJSON - values[1] // => commentsJSON - - return values; - }); - ``` - - @class Promise - @param {function} resolver - Useful for tooling. - @constructor - */ - function lib$es6$promise$promise$$Promise(resolver) { - this._id = lib$es6$promise$promise$$counter++; - this._state = undefined; - this._result = undefined; - this._subscribers = []; - - if (lib$es6$promise$$internal$$noop !== resolver) { - if (!lib$es6$promise$utils$$isFunction(resolver)) { - lib$es6$promise$promise$$needsResolver(); - } - - if (!(this instanceof lib$es6$promise$promise$$Promise)) { - lib$es6$promise$promise$$needsNew(); - } - - lib$es6$promise$$internal$$initializePromise(this, resolver); - } - } - - lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default; - lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default; - lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default; - lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default; - lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler; - lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap; - lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap; - - lib$es6$promise$promise$$Promise.prototype = { - constructor: lib$es6$promise$promise$$Promise, - - /** - The primary way of interacting with a promise is through its `then` method, - which registers callbacks to receive either a promise's eventual value or the - reason why the promise cannot be fulfilled. - - ```js - findUser().then(function(user){ - // user is available - }, function(reason){ - // user is unavailable, and you are given the reason why - }); - ``` - - Chaining - -------- - - The return value of `then` is itself a promise. This second, 'downstream' - promise is resolved with the return value of the first promise's fulfillment - or rejection handler, or rejected if the handler throws an exception. - - ```js - findUser().then(function (user) { - return user.name; - }, function (reason) { - return 'default name'; - }).then(function (userName) { - // If `findUser` fulfilled, `userName` will be the user's name, otherwise it - // will be `'default name'` - }); - - findUser().then(function (user) { - throw new Error('Found user, but still unhappy'); - }, function (reason) { - throw new Error('`findUser` rejected and we're unhappy'); - }).then(function (value) { - // never reached - }, function (reason) { - // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. - // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. - }); - ``` - If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. - - ```js - findUser().then(function (user) { - throw new PedagogicalException('Upstream error'); - }).then(function (value) { - // never reached - }).then(function (value) { - // never reached - }, function (reason) { - // The `PedgagocialException` is propagated all the way down to here - }); - ``` - - Assimilation - ------------ - - Sometimes the value you want to propagate to a downstream promise can only be - retrieved asynchronously. This can be achieved by returning a promise in the - fulfillment or rejection handler. The downstream promise will then be pending - until the returned promise is settled. This is called *assimilation*. - - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // The user's comments are now available - }); - ``` - - If the assimliated promise rejects, then the downstream promise will also reject. - - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // If `findCommentsByAuthor` fulfills, we'll have the value here - }, function (reason) { - // If `findCommentsByAuthor` rejects, we'll have the reason here - }); - ``` - - Simple Example - -------------- - - Synchronous Example - - ```javascript - var result; - - try { - result = findResult(); - // success - } catch(reason) { - // failure - } - ``` - - Errback Example - - ```js - findResult(function(result, err){ - if (err) { - // failure - } else { - // success - } - }); - ``` - - Promise Example; - - ```javascript - findResult().then(function(result){ - // success - }, function(reason){ - // failure - }); - ``` - - Advanced Example - -------------- - - Synchronous Example - - ```javascript - var author, books; - - try { - author = findAuthor(); - books = findBooksByAuthor(author); - // success - } catch(reason) { - // failure - } - ``` - - Errback Example - - ```js - - function foundBooks(books) { - - } - - function failure(reason) { - - } - - findAuthor(function(author, err){ - if (err) { - failure(err); - // failure - } else { - try { - findBoooksByAuthor(author, function(books, err) { - if (err) { - failure(err); - } else { - try { - foundBooks(books); - } catch(reason) { - failure(reason); - } - } - }); - } catch(error) { - failure(err); - } - // success - } - }); - ``` - - Promise Example; - - ```javascript - findAuthor(). - then(findBooksByAuthor). - then(function(books){ - // found books - }).catch(function(reason){ - // something went wrong - }); - ``` - - @method then - @param {Function} onFulfilled - @param {Function} onRejected - Useful for tooling. - @return {Promise} - */ - then: function(onFulfillment, onRejection) { - var parent = this; - var state = parent._state; - - if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) { - return this; - } - - var child = new this.constructor(lib$es6$promise$$internal$$noop); - var result = parent._result; - - if (state) { - var callback = arguments[state - 1]; - lib$es6$promise$asap$$asap(function(){ - lib$es6$promise$$internal$$invokeCallback(state, child, callback, result); - }); - } else { - lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection); - } - - return child; - }, - - /** - `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same - as the catch block of a try/catch statement. - - ```js - function findAuthor(){ - throw new Error('couldn't find that author'); - } - - // synchronous - try { - findAuthor(); - } catch(reason) { - // something went wrong - } - - // async with promises - findAuthor().catch(function(reason){ - // something went wrong - }); - ``` - - @method catch - @param {Function} onRejection - Useful for tooling. - @return {Promise} - */ - 'catch': function(onRejection) { - return this.then(null, onRejection); - } - }; - function lib$es6$promise$polyfill$$polyfill() { - var local; - - if (typeof global !== 'undefined') { - local = global; - } else if (typeof self !== 'undefined') { - local = self; - } else { - try { - local = Function('return this')(); - } catch (e) { - throw new Error('polyfill failed because global object is unavailable in this environment'); - } - } - - var P = local.Promise; - - if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) { - return; - } - - local.Promise = lib$es6$promise$promise$$default; - } - var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill; - - var lib$es6$promise$umd$$ES6Promise = { - 'Promise': lib$es6$promise$promise$$default, - 'polyfill': lib$es6$promise$polyfill$$default - }; - - /* global define:true module:true window: true */ - if (typeof define === 'function' && define['amd']) { - define(function() { return lib$es6$promise$umd$$ES6Promise; }); - } else if (typeof module !== 'undefined' && module['exports']) { - module['exports'] = lib$es6$promise$umd$$ES6Promise; - } else if (typeof this !== 'undefined') { - this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise; - } - - lib$es6$promise$polyfill$$default(); -}).call(this); - - -/** - * @constructor - */ -function DefaultHttpRequest() { - - /** - * @name _promiseFactory - * @type DefaultPromiseFactory - */ - - /** - * @param {XMLHttpRequest} xhr - * @param {object.} headers - */ - function setHeaders(xhr, headers) { - var keys = Object.keys(headers); - - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var value = headers[key]; - - xhr.setRequestHeader(key, value); - } - } - - /** - * @param {string} url - * @param {{ headers: object. }} [config] - * @returns {Promise} - */ - this.getJSON = function (url, config) { - return _promiseFactory.create(function (resolve, reject) { - - try { - var xhr = new XMLHttpRequest(); - xhr.open("GET", url); - xhr.responseType = "json"; - - if (config) { - if (config.headers) { - setHeaders(xhr, config.headers); - } - } - - xhr.onload = function () { - try { - if (xhr.status === 200) { - var response = ""; - // To support IE9 get the response from xhr.responseText not xhr.response - if (window.XDomainRequest) { - response = xhr.responseText; - } else { - response = xhr.response; - } - if (typeof response === "string") { - response = JSON.parse(response); - } - resolve(response); - } - else { - reject(Error(xhr.statusText + "(" + xhr.status + ")")); - } - } - catch (err) { - reject(err); - } - }; - - xhr.onerror = function () { - reject(Error("Network error")); - }; - - xhr.send(); - } - catch (err) { - return reject(err); - } - }); - }; -} - -_httpRequest = new DefaultHttpRequest(); - -/** - * @constructor - * @param {Promise} promise - */ -function DefaultPromise(promise) { - - /** - * @param {function(*):*} successCallback - * @param {function(*):*} errorCallback - * @returns {DefaultPromise} - */ - this.then = function (successCallback, errorCallback) { - var childPromise = promise.then(successCallback, errorCallback); - - return new DefaultPromise(childPromise); - }; - - /** - * - * @param {function(*):*} errorCallback - * @returns {DefaultPromise} - */ - this.catch = function (errorCallback) { - var childPromise = promise.catch(errorCallback); - - return new DefaultPromise(childPromise); - }; -} - -/** - * @constructor - */ -function DefaultPromiseFactory() { - - this.resolve = function (value) { - return new DefaultPromise(Promise.resolve(value)); - }; - - this.reject = function (reason) { - return new DefaultPromise(Promise.reject(reason)); - }; - - /** - * @param {function(resolve:function, reject:function)} callback - * @returns {DefaultPromise} - */ - this.create = function (callback) { - return new DefaultPromise(new Promise(callback)); - }; -} - -_promiseFactory = new DefaultPromiseFactory(); -/* - * Copyright 2015 Dominick Baier, Brock Allen - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -function log() { - //var param = [].join.call(arguments); - //console.log(param); -} - -function copy(obj, target) { - target = target || {}; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - target[key] = obj[key]; - } - } - return target; -} - -function rand() { - return ((Date.now() + Math.random()) * Math.random()).toString().replace(".", ""); -} - -function resolve(param) { - return _promiseFactory.resolve(param); -} - -function error(message) { - return _promiseFactory.reject(Error(message)); -} - -function parseOidcResult(queryString) { - log("parseOidcResult"); - - queryString = queryString || location.hash; - - var idx = queryString.lastIndexOf("#"); - if (idx >= 0) { - queryString = queryString.substr(idx + 1); - } - - var params = {}, - regex = /([^&=]+)=([^&]*)/g, - m; - - var counter = 0; - while (m = regex.exec(queryString)) { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); - if (counter++ > 50) { - return { - error: "Response exceeded expected number of parameters" - }; - } - } - - for (var prop in params) { - return params; - } -} - -function getJson(url, token) { - log("getJson", url); - - var config = {}; - - if (token) { - config.headers = {"Authorization": "Bearer " + token}; - } - - return _httpRequest.getJSON(url, config); -} - -function OidcClient(settings) { - this._settings = settings || {}; - - if (!this._settings.request_state_key) { - this._settings.request_state_key = "OidcClient.request_state"; - } - - if (!this._settings.request_state_store) { - this._settings.request_state_store = window.localStorage; - } - - if (typeof this._settings.load_user_profile === 'undefined') { - this._settings.load_user_profile = true; - } - - if (typeof this._settings.filter_protocol_claims === 'undefined') { - this._settings.filter_protocol_claims = true; - } - - if (this._settings.authority && this._settings.authority.indexOf('.well-known/openid-configuration') < 0) { - if (this._settings.authority[this._settings.authority.length - 1] !== '/') { - this._settings.authority += '/'; - } - this._settings.authority += '.well-known/openid-configuration'; - } - - if (!this._settings.response_type) { - this._settings.response_type = "id_token token"; - } - - Object.defineProperty(this, "isOidc", { - get: function () { - if (this._settings.response_type) { - var result = this._settings.response_type.split(/\s+/g).filter(function (item) { - return item === "id_token"; - }); - return !!(result[0]); - } - return false; - } - }); - - Object.defineProperty(this, "isOAuth", { - get: function () { - if (this._settings.response_type) { - var result = this._settings.response_type.split(/\s+/g).filter(function (item) { - return item === "token"; - }); - return !!(result[0]); - } - return false; - } - }); -} - -OidcClient.parseOidcResult = parseOidcResult; - -OidcClient.prototype.loadMetadataAsync = function () { - log("OidcClient.loadMetadataAsync"); - - var settings = this._settings; - - if (settings.metadata) { - return resolve(settings.metadata); - } - - if (!settings.authority) { - return error("No authority configured"); - } - - return getJson(settings.authority) - .then(function (metadata) { - settings.metadata = metadata; - return metadata; - }, function (err) { - return error("Failed to load metadata (" + err && err.message + ")"); - }); -}; - -OidcClient.prototype.loadX509SigningKeyAsync = function () { - log("OidcClient.loadX509SigningKeyAsync"); - - var settings = this._settings; - - function getKeyAsync(jwks) { - if (!jwks.keys || !jwks.keys.length) { - return error("Signing keys empty"); - } - - var key = jwks.keys[0]; - if (key.kty !== "RSA") { - return error("Signing key not RSA"); - } - - if (!key.x5c || !key.x5c.length) { - return error("RSA keys empty"); - } - - return resolve(key.x5c[0]); - } - - if (settings.jwks) { - return getKeyAsync(settings.jwks); - } - - return this.loadMetadataAsync().then(function (metadata) { - if (!metadata.jwks_uri) { - return error("Metadata does not contain jwks_uri"); - } - - return getJson(metadata.jwks_uri).then(function (jwks) { - settings.jwks = jwks; - return getKeyAsync(jwks); - }, function (err) { - return error("Failed to load signing keys (" + err && err.message + ")"); - }); - }); -}; - -OidcClient.prototype.loadUserProfile = function (access_token) { - log("OidcClient.loadUserProfile"); - - return this.loadMetadataAsync().then(function (metadata) { - - if (!metadata.userinfo_endpoint) { - return error("Metadata does not contain userinfo_endpoint"); - } - - return getJson(metadata.userinfo_endpoint, access_token); - }); -} - -OidcClient.prototype.loadAuthorizationEndpoint = function () { - log("OidcClient.loadAuthorizationEndpoint"); - - if (this._settings.authorization_endpoint) { - return resolve(this._settings.authorization_endpoint); - } - - if (!this._settings.authority) { - return error("No authorization_endpoint configured"); - } - - return this.loadMetadataAsync().then(function (metadata) { - if (!metadata.authorization_endpoint) { - return error("Metadata does not contain authorization_endpoint"); - } - - return metadata.authorization_endpoint; - }); -}; - -OidcClient.prototype.createTokenRequestAsync = function () { - log("OidcClient.createTokenRequestAsync"); - - var client = this; - var settings = client._settings; - - return client.loadAuthorizationEndpoint().then(function (authorization_endpoint) { - - var state = rand(); - var url = authorization_endpoint + "?state=" + encodeURIComponent(state); - - if (client.isOidc) { - var nonce = rand(); - url += "&nonce=" + encodeURIComponent(nonce); - } - - var required = ["client_id", "redirect_uri", "response_type", "scope"]; - required.forEach(function (key) { - var value = settings[key]; - if (value) { - url += "&" + key + "=" + encodeURIComponent(value); - } - }); - - var optional = ["prompt", "display", "max_age", "ui_locales", "id_token_hint", "login_hint", "acr_values"]; - optional.forEach(function (key) { - var value = settings[key]; - if (value) { - url += "&" + key + "=" + encodeURIComponent(value); - } - }); - - var request_state = { - oidc: client.isOidc, - oauth: client.isOAuth, - state: state - }; - - if (nonce) { - request_state["nonce"] = nonce; - } - - settings.request_state_store.setItem(settings.request_state_key, JSON.stringify(request_state)); - - return { - request_state: request_state, - url: url - }; - }); -} - -OidcClient.prototype.createLogoutRequestAsync = function (id_token_hint) { - log("OidcClient.createLogoutRequestAsync"); - - var settings = this._settings; - return this.loadMetadataAsync().then(function (metadata) { - if (!metadata.end_session_endpoint) { - return error("No end_session_endpoint in metadata"); - } - - var url = metadata.end_session_endpoint; - if (id_token_hint && settings.post_logout_redirect_uri) { - url += "?post_logout_redirect_uri=" + encodeURIComponent(settings.post_logout_redirect_uri); - url += "&id_token_hint=" + encodeURIComponent(id_token_hint); - } - return url; - }); -} - -OidcClient.prototype.validateIdTokenAsync = function (id_token, nonce, access_token) { - log("OidcClient.validateIdTokenAsync"); - - var client = this; - var settings = client._settings; - - return client.loadX509SigningKeyAsync().then(function (cert) { - - var jws = new KJUR.jws.JWS(); - if (jws.verifyJWSByPemX509Cert(id_token, cert)) { - var id_token_contents = JSON.parse(jws.parsedJWS.payloadS); - - if (nonce !== id_token_contents.nonce) { - return error("Invalid nonce"); - } - - return client.loadMetadataAsync().then(function (metadata) { - - if (id_token_contents.iss !== metadata.issuer) { - return error("Invalid issuer"); - } - - if (id_token_contents.aud !== settings.client_id) { - return error("Invalid audience"); - } - - var now = parseInt(Date.now() / 1000); - - // accept tokens issues up to 5 mins ago - var diff = now - id_token_contents.iat; - if (diff > (5 * 60)) { - return error("Token issued too long ago"); - } - - if (id_token_contents.exp < now) { - return error("Token expired"); - } - - if (access_token && settings.load_user_profile) { - // if we have an access token, then call user info endpoint - return client.loadUserProfile(access_token, id_token_contents).then(function (profile) { - return copy(profile, id_token_contents); - }); - } - else { - // no access token, so we have all our claims - return id_token_contents; - } - - }); - } - else { - return error("JWT failed to validate"); - } - - }); - -}; - -OidcClient.prototype.validateAccessTokenAsync = function (id_token_contents, access_token) { - log("OidcClient.validateAccessTokenAsync"); - - if (!id_token_contents.at_hash) { - return error("No at_hash in id_token"); - } - - var hash = KJUR.crypto.Util.sha256(access_token); - var left = hash.substr(0, hash.length / 2); - var left_b64u = hextob64u(left); - - if (left_b64u !== id_token_contents.at_hash) { - return error("at_hash failed to validate"); - } - - return resolve(); -}; - -OidcClient.prototype.validateIdTokenAndAccessTokenAsync = function (id_token, nonce, access_token) { - log("OidcClient.validateIdTokenAndAccessTokenAsync"); - - var client = this; - - return client.validateIdTokenAsync(id_token, nonce, access_token).then(function (id_token_contents) { - - return client.validateAccessTokenAsync(id_token_contents, access_token).then(function () { - - return id_token_contents; - - }); - - }); -} - -OidcClient.prototype.processResponseAsync = function (queryString) { - log("OidcClient.processResponseAsync"); - - var client = this; - var settings = client._settings; - - var request_state = settings.request_state_store.getItem(settings.request_state_key); - settings.request_state_store.removeItem(settings.request_state_key); - - if (!request_state) { - return error("No request state loaded"); - } - - request_state = JSON.parse(request_state); - if (!request_state) { - return error("No request state loaded"); - } - - if (!request_state.state) { - return error("No state loaded"); - } - - var result = parseOidcResult(queryString); - if (!result) { - return error("No OIDC response"); - } - - if (result.error) { - return error(result.error); - } - - if (result.state !== request_state.state) { - return error("Invalid state"); - } - - if (request_state.oidc) { - if (!result.id_token) { - return error("No identity token"); - } - - if (!request_state.nonce) { - return error("No nonce loaded"); - } - } - - if (request_state.oauth) { - if (!result.access_token) { - return error("No access token"); - } - - if (!result.token_type || result.token_type.toLowerCase() !== "bearer") { - return error("Invalid token type"); - } - - if (!result.expires_in) { - return error("No token expiration"); - } - } - - var promise = resolve(); - if (request_state.oidc && request_state.oauth) { - promise = client.validateIdTokenAndAccessTokenAsync(result.id_token, request_state.nonce, result.access_token); - } - else if (request_state.oidc) { - promise = client.validateIdTokenAsync(result.id_token, request_state.nonce); - } - - return promise.then(function (profile) { - if (profile && settings.filter_protocol_claims) { - var remove = ["nonce", "at_hash", "iat", "nbf", "exp", "aud", "iss"]; - remove.forEach(function (key) { - delete profile[key]; - }); - } - - return { - profile: profile, - id_token: result.id_token, - access_token: result.access_token, - expires_in: result.expires_in, - scope: result.scope, - session_state : result.session_state - }; - }); -} - - // exports - OidcClient._promiseFactory = _promiseFactory; - OidcClient._httpRequest = _httpRequest; - window.OidcClient = OidcClient; -})(); -(function () { - -/* -* Copyright 2014-2016 Dominick Baier, Brock Allen -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -// globals -var _promiseFactory = OidcClient._promiseFactory; -var _httpRequest = OidcClient._httpRequest; - -function copy(obj, target) { - target = target || {}; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - target[key] = obj[key]; - } - } - return target; -} - -function Token(other) { - if (other) { - this.profile = other.profile; - this.id_token = other.id_token; - this.access_token = other.access_token; - if (other.access_token) { - this.expires_at = parseInt(other.expires_at); - } - else if (other.id_token) { - this.expires_at = other.profile.exp; - } - else { - throw Error("Either access_token or id_token required."); - } - this.scope = other.scope; - this.session_state = other.session_state; - } - else { - this.expires_at = 0; - } - - Object.defineProperty(this, "scopes", { - get: function () { - return (this.scope || "").split(" "); - } - }); - - Object.defineProperty(this, "expired", { - get: function () { - var now = parseInt(Date.now() / 1000); - return this.expires_at < now; - } - }); - - Object.defineProperty(this, "expires_in", { - get: function () { - var now = parseInt(Date.now() / 1000); - return this.expires_at - now; - } - }); -} - -Token.fromResponse = function (response) { - if (response.access_token) { - var now = parseInt(Date.now() / 1000); - response.expires_at = now + parseInt(response.expires_in); - } - return new Token(response); -} - -Token.fromJSON = function (json) { - if (json) { - try { - var obj = JSON.parse(json); - return new Token(obj); - } - catch (e) { - } - } - return new Token(null); -} - -Token.prototype.toJSON = function () { - return JSON.stringify({ - profile: this.profile, - id_token: this.id_token, - access_token: this.access_token, - expires_at: this.expires_at, - scope: this.scopes.join(" "), - session_state: this.session_state - }); -} - -function FrameLoader(url, config) { - this.url = url; - config = config || {}; - config.cancelDelay = config.cancelDelay || 5000; - this.config = config; -} - -FrameLoader.prototype.loadAsync = function (url) { - var self = this; - url = url || this.url; - - if (!url) { - return _promiseFactory.reject(Error("No url provided")); - } - - return _promiseFactory.create(function (resolve, reject) { - var frame = window.document.createElement("iframe"); - frame.style.display = "none"; - - function cleanup() { - window.removeEventListener("message", message, false); - if (handle) { - window.clearTimeout(handle); - } - handle = null; - window.document.body.removeChild(frame); - } - - function cancel(e) { - cleanup(); - reject(); - } - - function message(e) { - if (handle && e.origin === location.protocol + "//" + location.host && e.source == frame.contentWindow) { - cleanup(); - resolve(e.data); - } - } - - var handle = window.setTimeout(cancel, self.config.cancelDelay); - window.addEventListener("message", message, false); - window.document.body.appendChild(frame); - frame.src = url; - }); -} - -function loadToken(mgr) { - mgr._token = null; - if (mgr._settings.persist) { - var tokenJson = mgr._settings.store.getItem(mgr._settings.persistKey); - if (tokenJson) { - var token = Token.fromJSON(tokenJson); - if (!token.expired) { - mgr._token = token; - } - } - } -} - -function configureTokenExpiring(mgr) { - - function callback() { - handle = null; - mgr._callTokenExpiring(); - } - - var handle = null; - - function cancel() { - if (handle) { - window.clearTimeout(handle); - handle = null; - } - } - - function setup(duration) { - handle = window.setTimeout(callback, duration * 1000); - } - - function configure() { - cancel(); - - if (!mgr.expired) { - var duration = mgr.expires_in; - if (duration > 60) { - setup(duration - 60); - } - else { - callback(); - } - } - } - - configure(); - - mgr.addOnTokenObtained(configure); - mgr.addOnTokenRemoved(cancel); -} - -function configureAutoRenewToken(mgr) { - - if (mgr._settings.silent_redirect_uri && mgr._settings.silent_renew) { - - mgr.addOnTokenExpiring(function () { - mgr.renewTokenSilentAsync().catch(function (e) { - mgr._callSilentTokenRenewFailed(); - console.error(e && e.message || "Unknown error"); - }); - }); - - } -} - -function configureTokenExpired(mgr) { - - function callback() { - handle = null; - - if (mgr._token) { - mgr.saveToken(null); - } - - mgr._callTokenExpired(); - } - - var handle = null; - - function cancel() { - if (handle) { - window.clearTimeout(handle); - handle = null; - } - } - - function setup(duration) { - handle = window.setTimeout(callback, duration * 1000); - } - - function configure() { - cancel(); - if (mgr.expires_in > 0) { - // register 1 second beyond expiration so we don't get into edge conditions for expiration - setup(mgr.expires_in + 1); - } - } - - configure(); - - mgr.addOnTokenObtained(configure); - mgr.addOnTokenRemoved(cancel); -} - -function TokenManager(settings) { - this._settings = settings || {}; - - if (typeof this._settings.persist === 'undefined') { - this._settings.persist = true; - } - this._settings.store = this._settings.store || window.localStorage; - this._settings.persistKey = this._settings.persistKey || "TokenManager.token"; - - this.oidcClient = new OidcClient(this._settings); - - this._callbacks = { - tokenRemovedCallbacks: [], - tokenExpiringCallbacks: [], - tokenExpiredCallbacks: [], - tokenObtainedCallbacks: [], - silentTokenRenewFailedCallbacks: [] - }; - - Object.defineProperty(this, "profile", { - get: function () { - if (this._token) { - return this._token.profile; - } - } - }); - Object.defineProperty(this, "id_token", { - get: function () { - if (this._token) { - return this._token.id_token; - } - } - }); - Object.defineProperty(this, "access_token", { - get: function () { - if (this._token && !this._token.expired) { - return this._token.access_token; - } - } - }); - Object.defineProperty(this, "expired", { - get: function () { - if (this._token) { - return this._token.expired; - } - return true; - } - }); - Object.defineProperty(this, "expires_in", { - get: function () { - if (this._token) { - return this._token.expires_in; - } - return 0; - } - }); - Object.defineProperty(this, "expires_at", { - get: function () { - if (this._token) { - return this._token.expires_at; - } - return 0; - } - }); - Object.defineProperty(this, "scope", { - get: function () { - return this._token && this._token.scope; - } - }); - Object.defineProperty(this, "scopes", { - get: function () { - if (this._token) { - return [].concat(this._token.scopes); - } - return []; - } - }); - Object.defineProperty(this, "session_state", { - get: function () { - if (this._token) { - return this._token.session_state; - } - } - }); - - var mgr = this; - loadToken(mgr); - if (mgr._settings.store instanceof window.localStorage.constructor) { - window.addEventListener("storage", function (e) { - if (e.key === mgr._settings.persistKey) { - loadToken(mgr); - - if (mgr._token) { - mgr._callTokenObtained(); - } - else { - mgr._callTokenRemoved(); - } - } - }); - } - configureTokenExpired(mgr); - configureAutoRenewToken(mgr); - - // delay this so consuming apps can register for callbacks first - window.setTimeout(function () { - configureTokenExpiring(mgr); - }, 0); -} - -/** - * @param {{ create:function(successCallback:function(), errorCallback:function()):Promise, resolve:function(value:*):Promise, reject:function():Promise}} promiseFactory - */ -TokenManager.setPromiseFactory = function (promiseFactory) { - _promiseFactory = promiseFactory; -}; - -/** - * @param {{getJSON:function(url:string, config:{ headers: object. })}} httpRequest - */ -TokenManager.setHttpRequest = function (httpRequest) { - if ((typeof httpRequest !== 'object') || (typeof httpRequest.getJSON !== 'function')) { - throw Error('The provided value is not a valid http request.'); - } - - _httpRequest = httpRequest; -}; - -TokenManager.prototype._callTokenRemoved = function () { - this._callbacks.tokenRemovedCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype._callTokenExpiring = function () { - this._callbacks.tokenExpiringCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype._callTokenExpired = function () { - this._callbacks.tokenExpiredCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype._callTokenObtained = function () { - this._callbacks.tokenObtainedCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype._callSilentTokenRenewFailed = function () { - this._callbacks.silentTokenRenewFailedCallbacks.forEach(function (cb) { - cb(); - }); -} - -TokenManager.prototype.saveToken = function (token) { - if (token && !(token instanceof Token)) { - token = Token.fromResponse(token); - } - - this._token = token; - - if (this._settings.persist && !this.expired) { - this._settings.store.setItem(this._settings.persistKey, token.toJSON()); - } - else { - this._settings.store.removeItem(this._settings.persistKey); - } - - if (token) { - this._callTokenObtained(); - } - else { - this._callTokenRemoved(); - } -} - -TokenManager.prototype.addOnTokenRemoved = function (cb) { - this._callbacks.tokenRemovedCallbacks.push(cb); -} - -TokenManager.prototype.addOnTokenObtained = function (cb) { - this._callbacks.tokenObtainedCallbacks.push(cb); -} - -TokenManager.prototype.addOnTokenExpiring = function (cb) { - this._callbacks.tokenExpiringCallbacks.push(cb); -} - -TokenManager.prototype.addOnTokenExpired = function (cb) { - this._callbacks.tokenExpiredCallbacks.push(cb); -} - -TokenManager.prototype.addOnSilentTokenRenewFailed = function (cb) { - this._callbacks.silentTokenRenewFailedCallbacks.push(cb); -} - -TokenManager.prototype.removeToken = function () { - this.saveToken(null); -} - -TokenManager.prototype.redirectForToken = function () { - var oidc = this.oidcClient; - return oidc.createTokenRequestAsync().then(function (request) { - window.location = request.url; - }, function (err) { - console.error("TokenManager.redirectForToken error: " + (err && err.message || "Unknown error")); - return _promiseFactory.reject(err); - }); -} - -TokenManager.prototype.redirectForLogout = function () { - var mgr = this; - return mgr.oidcClient.createLogoutRequestAsync(mgr.id_token).then(function (url) { - mgr.removeToken(); - window.location = url; - }, function (err) { - console.error("TokenManager.redirectForLogout error: " + (err && err.message || "Unknown error")); - return _promiseFactory.reject(err); - }); -} - -TokenManager.prototype.processTokenCallbackAsync = function (queryString) { - var mgr = this; - return mgr.oidcClient.processResponseAsync(queryString).then(function (token) { - mgr.saveToken(token); - }); -} - -TokenManager.prototype.renewTokenSilentAsync = function () { - var mgr = this; - - if (!mgr._settings.silent_redirect_uri) { - return _promiseFactory.reject(Error("silent_redirect_uri not configured")); - } - - var settings = copy(mgr._settings); - settings.redirect_uri = settings.silent_redirect_uri; - if (!settings.prompt) { - settings.prompt = "none"; - } - - var oidc = new OidcClient(settings); - return oidc.createTokenRequestAsync().then(function (request) { - var frame = new FrameLoader(request.url, { cancelDelay: mgr._settings.silent_renew_timeout }); - return frame.loadAsync().then(function (hash) { - return oidc.processResponseAsync(hash).then(function (token) { - mgr.saveToken(token); - }); - }); - }); -} - -TokenManager.prototype.processTokenCallbackSilent = function (hash) { - if (window.parent && window !== window.parent) { - var hash = hash || window.location.hash; - if (hash) { - window.parent.postMessage(hash, location.protocol + "//" + location.host); - } - } -} - -TokenManager.prototype.openPopupForTokenAsync = function (popupSettings) { - popupSettings = popupSettings || {}; - popupSettings.features = popupSettings.features || "location=no,toolbar=no"; - popupSettings.target = popupSettings.target || "_blank"; - - var callback_prefix = "tokenmgr_callback_"; - - // this is a shared callback - if (!window.openPopupForTokenAsyncCallback) { - window.openPopupForTokenAsyncCallback = function (hash) { - var result = OidcClient.parseOidcResult(hash); - if (result && result.state && window[callback_prefix + result.state]) { - window[callback_prefix + result.state](hash); - } - } - } - - var mgr = this; - var settings = copy(mgr._settings); - settings.redirect_uri = settings.popup_redirect_uri || settings.redirect_uri; - - if (mgr._pendingPopup) { - return _promiseFactory.create(function (resolve, reject) { - reject(Error("Already a pending popup token request.")); - }); - } - - var popup = window.open(settings.redirect_uri, popupSettings.target, popupSettings.features); - if (!popup) { - return _promiseFactory.create(function (resolve, reject) { - reject(Error("Error opening popup.")); - }); - } - - mgr._pendingPopup = true; - - function cleanup(name) { - if (handle) { - window.clearInterval(handle); - } - popup.close(); - delete mgr._pendingPopup; - if (name) { - delete window[name]; - } - } - - var reject_popup; - function checkClosed() { - if (!popup.window) { - cleanup(); - reject_popup(Error("Popup closed")); - } - } - var handle = window.setInterval(checkClosed, 1000); - - return _promiseFactory.create(function (resolve, reject) { - reject_popup = reject; - - var oidc = new OidcClient(settings); - oidc.createTokenRequestAsync().then(function (request) { - - var callback_name = callback_prefix + request.request_state.state; - window[callback_name] = function (hash) { - cleanup(callback_name); - - oidc.processResponseAsync(hash).then(function (token) { - mgr.saveToken(token); - resolve(); - }, function (err) { - reject(err); - }); - }; - - // give the popup 5 seconds to ready itself, otherwise fail - var seconds_to_wait = 5; - var interval = 500; - var total_times = (seconds_to_wait*1000) / interval; - var count = 0; - function redirectPopup() { - if (popup.setUrl) { - popup.setUrl(request.url); - } - else if (count < total_times) { - count++; - window.setTimeout(redirectPopup, interval); - } - else { - cleanup(callback_name); - reject(Error("Timeout error on popup")); - } - } - redirectPopup(); - }, function (err) { - cleanup(); - reject(err); - }); - }); -} - -TokenManager.prototype.processTokenPopup = function (hash) { - hash = hash || window.location.hash; - - window.setUrl = function (url) { - window.location = url; - } - - if (hash) { - window.opener.openPopupForTokenAsyncCallback(hash); - } -} - - - // exports - window.OidcTokenManager = TokenManager; -})(); diff --git a/src/Services/Basket/Basket.API/Auth/Client/popup.html b/src/Services/Basket/Basket.API/Auth/Client/popup.html deleted file mode 100644 index 364f8d7dd..000000000 --- a/src/Services/Basket/Basket.API/Auth/Client/popup.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs b/src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs deleted file mode 100644 index 2acda7be8..000000000 --- a/src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; - -public class AuthorizationHeaderParameterOperationFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; - var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); - var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); - - if (isAuthorized && !allowAnonymous) - { - operation.Parameters ??= new List(); - - - operation.Parameters.Add(new OpenApiParameter - { - Name = "Authorization", - In = ParameterLocation.Header, - Description = "access token", - Required = true - }); - } - } -} diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index ebb224824..173169da1 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -1,60 +1,25 @@  - net6.0 - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + net7.0 ..\..\..\..\docker-compose.dcproj - false - true + 2964ec8e-0d48-4541-b305-94cab537f867 - - PreserveNewest - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - + - - - + diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index 5468bbc15..b971c96e9 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -56,7 +56,7 @@ public class BasketController : ControllerBase return BadRequest(); } - var userName = this.HttpContext.User.FindFirst(x => x.Type == ClaimTypes.Name).Value; + var userName = User.FindFirst(x => x.Type == ClaimTypes.Name).Value; var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street, basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName, @@ -71,7 +71,7 @@ public class BasketController : ControllerBase } catch (Exception ex) { - _logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName); + _logger.LogError(ex, "Error Publishing integration event: {IntegrationEventId}", eventMessage.Id); throw; } diff --git a/src/Services/Basket/Basket.API/Controllers/HomeController.cs b/src/Services/Basket/Basket.API/Controllers/HomeController.cs deleted file mode 100644 index 8b2b7c2e7..000000000 --- a/src/Services/Basket/Basket.API/Controllers/HomeController.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers; - -public class HomeController : Controller -{ - // GET: // - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } -} - diff --git a/src/Services/Basket/Basket.API/CustomExtensionMethods.cs b/src/Services/Basket/Basket.API/CustomExtensionMethods.cs deleted file mode 100644 index 8fcbf0c28..000000000 --- a/src/Services/Basket/Basket.API/CustomExtensionMethods.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API; - -public static class CustomExtensionMethods -{ - public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) - { - var hcBuilder = services.AddHealthChecks(); - - hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); - - hcBuilder - .AddRedis( - configuration["ConnectionString"], - name: "redis-check", - tags: new string[] { "redis" }); - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - hcBuilder - .AddAzureServiceBusTopic( - configuration["EventBusConnection"], - topicName: "eshop_event_bus", - name: "basket-servicebus-check", - tags: new string[] { "servicebus" }); - } - else - { - hcBuilder - .AddRabbitMQ( - $"amqp://{configuration["EventBusConnection"]}", - name: "basket-rabbitmqbus-check", - tags: new string[] { "rabbitmqbus" }); - } - - return services; - } -} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Dockerfile b/src/Services/Basket/Basket.API/Dockerfile index 9cd4abba8..71bfc1547 100644 --- a/src/Services/Basket/Basket.API/Dockerfile +++ b/src/Services/Basket/Basket.API/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles @@ -11,7 +11,6 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" -COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj" COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" @@ -33,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" @@ -42,6 +42,7 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj" COPY "docker-compose.dcproj" "docker-compose.dcproj" +COPY "Directory.Packages.props" "Directory.Packages.props" COPY "NuGet.config" "NuGet.config" RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln" diff --git a/src/Services/Basket/Basket.API/Dockerfile.develop b/src/Services/Basket/Basket.API/Dockerfile.develop index 8f8312673..b29a1aa8a 100644 --- a/src/Services/Basket/Basket.API/Dockerfile.develop +++ b/src/Services/Basket/Basket.API/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/src/Services/Basket/Basket.API/Extensions/Extensions.cs b/src/Services/Basket/Basket.API/Extensions/Extensions.cs new file mode 100644 index 000000000..7fce10b50 --- /dev/null +++ b/src/Services/Basket/Basket.API/Extensions/Extensions.cs @@ -0,0 +1,20 @@ +public static class Extensions +{ + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + services.AddHealthChecks() + .AddRedis(_ => configuration.GetRequiredConnectionString("redis"), "redis", tags: new[] { "ready", "liveness" }); + + return services; + } + + public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration) + { + return services.AddSingleton(sp => + { + var redisConfig = ConfigurationOptions.Parse(configuration.GetRequiredConnectionString("redis"), true); + + return ConnectionMultiplexer.Connect(redisConfig); + }); + } +} diff --git a/src/Services/Basket/Basket.API/GlobalUsings.cs b/src/Services/Basket/Basket.API/GlobalUsings.cs index 75f7a878e..9a8376d4d 100644 --- a/src/Services/Basket/Basket.API/GlobalUsings.cs +++ b/src/Services/Basket/Basket.API/GlobalUsings.cs @@ -1,61 +1,29 @@ -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using Azure.Core; -global using Azure.Identity; -global using Basket.API.Infrastructure.ActionResults; -global using Basket.API.Infrastructure.Exceptions; -global using Basket.API.Infrastructure.Filters; -global using Basket.API.Infrastructure.Middlewares; -global using Basket.API.IntegrationEvents.EventHandling; -global using Basket.API.IntegrationEvents.Events; -global using Basket.API.Model; -global using Grpc.Core; -global using GrpcBasket; -global using HealthChecks.UI.Client; -global using Microsoft.AspNetCore.Authentication.JwtBearer; -global using Microsoft.AspNetCore.Authorization; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.AspNetCore.Http.Features; -global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Mvc.Authorization; -global using Microsoft.AspNetCore.Mvc.Filters; -global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore.Server.Kestrel.Core; -global using Microsoft.AspNetCore; -global using Azure.Messaging.ServiceBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; -global using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; -global using Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; -global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; -global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; -global using Microsoft.eShopOnContainers.Services.Basket.API.Model; -global using Microsoft.eShopOnContainers.Services.Basket.API.Services; -global using Microsoft.eShopOnContainers.Services.Basket.API; -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using Microsoft.OpenApi.Models; -global using RabbitMQ.Client; -global using Serilog.Context; -global using Serilog; -global using StackExchange.Redis; -global using Swashbuckle.AspNetCore.SwaggerGen; -global using System.Collections.Generic; -global using System.ComponentModel.DataAnnotations; -global using System.IdentityModel.Tokens.Jwt; -global using System.IO; -global using System.Linq; -global using System.Net; -global using System.Security.Claims; -global using System.Text.Json; -global using System.Threading.Tasks; -global using System; \ No newline at end of file +global using System; +global using System.Collections.Generic; +global using System.ComponentModel.DataAnnotations; +global using System.Linq; +global using System.Net; +global using System.Security.Claims; +global using System.Text.Json; +global using System.Threading.Tasks; +global using Basket.API.IntegrationEvents.EventHandling; +global using Basket.API.IntegrationEvents.Events; +global using Basket.API.Model; +global using Basket.API.Repositories; +global using Grpc.Core; +global using GrpcBasket; +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; +global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; +global using Microsoft.eShopOnContainers.Services.Basket.API.Model; +global using Microsoft.eShopOnContainers.Services.Basket.API.Services; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using Services.Common; +global using StackExchange.Redis; diff --git a/src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs b/src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs deleted file mode 100644 index 5f95e586e..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Basket.API.Infrastructure.ActionResults; - -public class InternalServerErrorObjectResult : ObjectResult -{ - public InternalServerErrorObjectResult(object error) - : base(error) - { - StatusCode = StatusCodes.Status500InternalServerError; - } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs b/src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs deleted file mode 100644 index 0502b7924..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Basket.API.Infrastructure.Exceptions; - -public class BasketDomainException : Exception -{ - public BasketDomainException() - { } - - public BasketDomainException(string message) - : base(message) - { } - - public BasketDomainException(string message, Exception innerException) - : base(message, innerException) - { } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs b/src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs deleted file mode 100644 index 66f55dddd..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -public static class FailingMiddlewareAppBuilderExtensions -{ - public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder) - { - return UseFailingMiddleware(builder, null); - } - - public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action action) - { - var options = new FailingOptions(); - action?.Invoke(options); - builder.UseMiddleware(options); - return builder; - } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs deleted file mode 100644 index 00b0b5195..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Basket.API.Infrastructure.Filters; - -public partial class HttpGlobalExceptionFilter : IExceptionFilter -{ - private readonly IWebHostEnvironment env; - private readonly ILogger logger; - - public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger logger) - { - this.env = env; - this.logger = logger; - } - - public void OnException(ExceptionContext context) - { - logger.LogError(new EventId(context.Exception.HResult), - context.Exception, - context.Exception.Message); - - if (context.Exception.GetType() == typeof(BasketDomainException)) - { - var json = new JsonErrorResponse - { - Messages = new[] { context.Exception.Message } - }; - - context.Result = new BadRequestObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - else - { - var json = new JsonErrorResponse - { - Messages = new[] { "An error occurred. Try it again." } - }; - - if (env.IsDevelopment()) - { - json.DeveloperMessage = context.Exception; - } - - context.Result = new InternalServerErrorObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - context.ExceptionHandled = true; - } -} diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs deleted file mode 100644 index 88bc02eda..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Basket.API.Infrastructure.Filters; - -public class JsonErrorResponse -{ - public string[] Messages { get; set; } - - public object DeveloperMessage { get; set; } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs deleted file mode 100644 index 5c97b85dc..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Basket.API.Infrastructure.Filters; - -public class ValidateModelStateFilter : ActionFilterAttribute -{ - public override void OnActionExecuting(ActionExecutingContext context) - { - if (context.ModelState.IsValid) - { - return; - } - - var validationErrors = context.ModelState - .Keys - .SelectMany(k => context.ModelState[k].Errors) - .Select(e => e.ErrorMessage) - .ToArray(); - - var json = new JsonErrorResponse - { - Messages = validationErrors - }; - - context.Result = new BadRequestObjectResult(json); - } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs b/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs deleted file mode 100644 index 60fbdb655..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -using Microsoft.Extensions.Logging; - -public class FailingMiddleware -{ - private readonly RequestDelegate _next; - private bool _mustFail; - private readonly FailingOptions _options; - private readonly ILogger _logger; - - public FailingMiddleware(RequestDelegate next, ILogger logger, FailingOptions options) - { - _next = next; - _options = options; - _mustFail = false; - _logger = logger; - } - - public async Task Invoke(HttpContext context) - { - var path = context.Request.Path; - if (path.Equals(_options.ConfigPath, StringComparison.OrdinalIgnoreCase)) - { - await ProcessConfigRequest(context); - return; - } - - if (MustFail(context)) - { - _logger.LogInformation("Response for path {Path} will fail.", path); - context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync("Failed due to FailingMiddleware enabled."); - } - else - { - await _next.Invoke(context); - } - } - - private async Task ProcessConfigRequest(HttpContext context) - { - var enable = context.Request.Query.Keys.Any(k => k == "enable"); - var disable = context.Request.Query.Keys.Any(k => k == "disable"); - - if (enable && disable) - { - throw new ArgumentException("Must use enable or disable querystring values, but not both"); - } - - if (disable) - { - _mustFail = false; - await SendOkResponse(context, "FailingMiddleware disabled. Further requests will be processed."); - return; - } - if (enable) - { - _mustFail = true; - await SendOkResponse(context, "FailingMiddleware enabled. Further requests will return HTTP 500"); - return; - } - - // If reach here, that means that no valid parameter has been passed. Just output status - await SendOkResponse(context, string.Format("FailingMiddleware is {0}", _mustFail ? "enabled" : "disabled")); - return; - } - - private async Task SendOkResponse(HttpContext context, string message) - { - context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync(message); - } - - private bool MustFail(HttpContext context) - { - var rpath = context.Request.Path.Value; - - if (_options.NotFilteredPaths.Any(p => p.Equals(rpath, StringComparison.InvariantCultureIgnoreCase))) - { - return false; - } - - return _mustFail && - (_options.EndpointPaths.Any(x => x == rpath) - || _options.EndpointPaths.Count == 0); - } -} diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs b/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs deleted file mode 100644 index 7818938d2..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -public class FailingOptions -{ - public string ConfigPath = "/Failing"; - public List EndpointPaths { get; set; } = new List(); - - public List NotFilteredPaths { get; set; } = new List(); -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs deleted file mode 100644 index 74da62b5d..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -public class FailingStartupFilter : IStartupFilter -{ - private readonly Action _options; - public FailingStartupFilter(Action optionsAction) - { - _options = optionsAction; - } - - public Action Configure(Action next) - { - return app => - { - app.UseFailingMiddleware(_options); - next(app); - }; - } -} - diff --git a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs b/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs deleted file mode 100644 index 8a8ba9523..000000000 --- a/src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Basket.API.Infrastructure.Middlewares; - -public static class WebHostBuildertExtensions -{ - public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, Action options) - { - builder.ConfigureServices(services => - { - services.AddSingleton(new FailingStartupFilter(options)); - }); - return builder; - } -} - diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs index 2c93f82fd..8796197c0 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs @@ -15,9 +15,9 @@ public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); await _repository.DeleteBasketAsync(@event.UserId.ToString()); } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs index b389b73d7..94c39cd5e 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs @@ -15,9 +15,9 @@ public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandl public async Task Handle(ProductPriceChangedIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var userIds = _repository.GetUsers(); @@ -36,7 +36,7 @@ public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandl if (itemsToUpdate != null) { - _logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate); + _logger.LogInformation("ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate); foreach (var item in itemsToUpdate) { diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index fd57afc82..28f3b1b1e 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -1,101 +1,30 @@ -var configuration = GetConfiguration(); +var builder = WebApplication.CreateBuilder(args); -Log.Logger = CreateSerilogLogger(configuration); +builder.AddServiceDefaults(); -try -{ - Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); - var host = BuildWebHost(configuration, args); +builder.Services.AddGrpc(); +builder.Services.AddControllers(); +builder.Services.AddProblemDetails(); - Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); - host.Run(); +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddRedis(builder.Configuration); - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} +builder.Services.AddTransient(); +builder.Services.AddTransient(); -IWebHost BuildWebHost(IConfiguration configuration, string[] args) => - WebHost.CreateDefaultBuilder(args) - .CaptureStartupErrors(false) - .ConfigureKestrel(options => - { - var ports = GetDefinedPorts(configuration); - options.Listen(IPAddress.Any, ports.httpPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); +builder.Services.AddTransient(); +builder.Services.AddTransient(); - options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http2; - }); +var app = builder.Build(); - }) - .ConfigureAppConfiguration(x => x.AddConfiguration(configuration)) - .UseFailing(options => - { - options.ConfigPath = "/Failing"; - options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" }); - }) - .UseStartup() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseSerilog() - .Build(); +app.UseServiceDefaults(); -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) - .ReadFrom.Configuration(configuration) - .CreateLogger(); -} +app.MapGrpcService(); +app.MapControllers(); -IConfiguration GetConfiguration() -{ - var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddEnvironmentVariables(); +var eventBus = app.Services.GetRequiredService(); - var config = builder.Build(); +eventBus.Subscribe(); +eventBus.Subscribe(); - if (config.GetValue("UseVault", false)) - { - TokenCredential credential = new ClientSecretCredential( - config["Vault:TenantId"], - config["Vault:ClientId"], - config["Vault:ClientSecret"]); - builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential); - } - - return builder.Build(); -} - -(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) -{ - var grpcPort = config.GetValue("GRPC_PORT", 5001); - var port = config.GetValue("PORT", 80); - return (port, grpcPort); -} - -public partial class Program -{ - - public static string Namespace = typeof(Startup).Namespace; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); -} +await app.RunAsync(); diff --git a/src/Services/Basket/Basket.API/Properties/launchSettings.json b/src/Services/Basket/Basket.API/Properties/launchSettings.json index 60a56b153..d54621f97 100644 --- a/src/Services/Basket/Basket.API/Properties/launchSettings.json +++ b/src/Services/Basket/Basket.API/Properties/launchSettings.json @@ -1,26 +1,11 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:58017/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Microsoft.eShopOnContainers.Services.Basket.API": { + "Basket.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:55103/", + "applicationUrl": "http://localhost:5221", "environmentVariables": { + "Identity__Url": "http://localhost:5223", "ASPNETCORE_ENVIRONMENT": "Development" } } diff --git a/src/Services/Basket/Basket.API/Properties/serviceDependencies.json b/src/Services/Basket/Basket.API/Properties/serviceDependencies.json new file mode 100644 index 000000000..adb08c8f2 --- /dev/null +++ b/src/Services/Basket/Basket.API/Properties/serviceDependencies.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "secrets1": { + "type": "secrets" + }, + "rabbitmq1": { + "type": "rabbitmq", + "connectionId": "eventbus", + "dynamicId": null + }, + "redis1": { + "type": "redis", + "connectionId": "ConnectionStrings:Redis", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Properties/serviceDependencies.local.json b/src/Services/Basket/Basket.API/Properties/serviceDependencies.local.json new file mode 100644 index 000000000..8fb5df48a --- /dev/null +++ b/src/Services/Basket/Basket.API/Properties/serviceDependencies.local.json @@ -0,0 +1,26 @@ +{ + "dependencies": { + "secrets1": { + "type": "secrets.user" + }, + "rabbitmq1": { + "containerPorts": "5672:5672,15672:15672", + "secretStore": "LocalSecretsFile", + "containerName": "rabbitmq", + "containerImage": "rabbitmq:3-management-alpine", + "type": "rabbitmq.container", + "connectionId": "eventbus", + "dynamicId": null + }, + "redis1": { + "serviceConnectorResourceId": "", + "containerPorts": "6379:6379", + "secretStore": "LocalSecretsFile", + "containerName": "basket-redis", + "containerImage": "redis:alpine", + "type": "redis.container", + "connectionId": "ConnectionStrings:Redis", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs b/src/Services/Basket/Basket.API/Repositories/RedisBasketRepository.cs similarity index 86% rename from src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs rename to src/Services/Basket/Basket.API/Repositories/RedisBasketRepository.cs index 7db463f20..bca10ca33 100644 --- a/src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs +++ b/src/Services/Basket/Basket.API/Repositories/RedisBasketRepository.cs @@ -1,4 +1,4 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; +namespace Basket.API.Repositories; public class RedisBasketRepository : IBasketRepository { @@ -6,9 +6,9 @@ public class RedisBasketRepository : IBasketRepository private readonly ConnectionMultiplexer _redis; private readonly IDatabase _database; - public RedisBasketRepository(ILoggerFactory loggerFactory, ConnectionMultiplexer redis) + public RedisBasketRepository(ILogger logger, ConnectionMultiplexer redis) { - _logger = loggerFactory.CreateLogger(); + _logger = logger; _redis = redis; _database = redis.GetDatabase(); } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs deleted file mode 100644 index 7e0142c2c..000000000 --- a/src/Services/Basket/Basket.API/Startup.cs +++ /dev/null @@ -1,285 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API; -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public virtual IServiceProvider ConfigureServices(IServiceCollection services) - { - services.AddGrpc(options => - { - options.EnableDetailedErrors = true; - }); - - RegisterAppInsights(services); - - services.AddControllers(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - options.Filters.Add(typeof(ValidateModelStateFilter)); - - }) // Added for functional tests - .AddApplicationPart(typeof(BasketController).Assembly) - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - - services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "eShopOnContainers - Basket HTTP API", - Version = "v1", - Description = "The Basket Service HTTP API" - }); - - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - AuthorizationUrl = new Uri($"{Configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), - TokenUrl = new Uri($"{Configuration.GetValue("IdentityUrlExternal")}/connect/token"), - Scopes = new Dictionary() - { - { "basket", "Basket API" } - } - } - } - }); - - options.OperationFilter(); - }); - - ConfigureAuthService(services); - - services.AddCustomHealthCheck(Configuration); - - services.Configure(Configuration); - - //By connecting here we are making sure that our service - //cannot start until redis is ready. This might slow down startup, - //but given that there is a delay on resolving the ip address - //and then creating the connection it seems reasonable to move - //that cost to startup instead of having the first request pay the - //penalty. - services.AddSingleton(sp => - { - var settings = sp.GetRequiredService>().Value; - var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); - - return ConnectionMultiplexer.Connect(configuration); - }); - - - if (Configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var serviceBusConnectionString = Configuration["EventBusConnection"]; - - return new DefaultServiceBusPersisterConnection(serviceBusConnectionString); - }); - } - else - { - services.AddSingleton(sp => - { - var logger = sp.GetRequiredService>(); - - var factory = new ConnectionFactory() - { - HostName = Configuration["EventBusConnection"], - DispatchConsumersAsync = true - }; - - if (!string.IsNullOrEmpty(Configuration["EventBusUserName"])) - { - factory.UserName = Configuration["EventBusUserName"]; - } - - if (!string.IsNullOrEmpty(Configuration["EventBusPassword"])) - { - factory.Password = Configuration["EventBusPassword"]; - } - - var retryCount = 5; - if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(Configuration["EventBusRetryCount"]); - } - - return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); - }); - } - - RegisterEventBus(services); - - - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .SetIsOriginAllowed((host) => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); - }); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - - services.AddOptions(); - - var container = new ContainerBuilder(); - container.Populate(services); - - return new AutofacServiceProvider(container.Build()); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - //loggerFactory.AddAzureWebAppDiagnostics(); - //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); - - var pathBase = Configuration["PATH_BASE"]; - if (!string.IsNullOrEmpty(pathBase)) - { - app.UsePathBase(pathBase); - } - - app.UseSwagger() - .UseSwaggerUI(setup => - { - setup.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Basket.API V1"); - setup.OAuthClientId("basketswaggerui"); - setup.OAuthAppName("Basket Swagger UI"); - }); - - app.UseRouting(); - app.UseCors("CorsPolicy"); - ConfigureAuth(app); - - app.UseStaticFiles(); - - app.UseEndpoints(endpoints => - { - endpoints.MapGrpcService(); - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapGet("/_proto/", async ctx => - { - ctx.Response.ContentType = "text/plain"; - using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); - using var sr = new StreamReader(fs); - while (!sr.EndOfStream) - { - var line = await sr.ReadLineAsync(); - if (line != "/* >>" || line != "<< */") - { - await ctx.Response.WriteAsync(line); - } - } - }); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - }); - - ConfigureEventBus(app); - } - - private void RegisterAppInsights(IServiceCollection services) - { - services.AddApplicationInsightsTelemetry(Configuration); - services.AddApplicationInsightsKubernetesEnricher(); - } - - private void ConfigureAuthService(IServiceCollection services) - { - // prevent from mapping "sub" claim to nameidentifier. - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); - - var identityUrl = Configuration.GetValue("IdentityUrl"); - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - - }).AddJwtBearer(options => - { - options.Authority = identityUrl; - options.RequireHttpsMetadata = false; - options.Audience = "basket"; - }); - } - - protected virtual void ConfigureAuth(IApplicationBuilder app) - { - app.UseAuthentication(); - app.UseAuthorization(); - } - - private void RegisterEventBus(IServiceCollection services) - { - if (Configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var serviceBusPersisterConnection = sp.GetRequiredService(); - var iLifetimeScope = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubscriptionsManager = sp.GetRequiredService(); - string subscriptionName = Configuration["SubscriptionClientName"]; - - return new EventBusServiceBus(serviceBusPersisterConnection, logger, - eventBusSubscriptionsManager, iLifetimeScope, subscriptionName); - }); - } - else - { - services.AddSingleton(sp => - { - var subscriptionClientName = Configuration["SubscriptionClientName"]; - var rabbitMQPersistentConnection = sp.GetRequiredService(); - var iLifetimeScope = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubscriptionsManager = sp.GetRequiredService(); - - var retryCount = 5; - if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(Configuration["EventBusRetryCount"]); - } - - return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubscriptionsManager, subscriptionClientName, retryCount); - }); - } - - services.AddSingleton(); - - services.AddTransient(); - services.AddTransient(); - } - - private void ConfigureEventBus(IApplicationBuilder app) - { - var eventBus = app.ApplicationServices.GetRequiredService(); - - eventBus.Subscribe(); - eventBus.Subscribe(); - } -} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/TestHttpResponseTrailersFeature.cs b/src/Services/Basket/Basket.API/TestHttpResponseTrailersFeature.cs deleted file mode 100644 index b1cfef87b..000000000 --- a/src/Services/Basket/Basket.API/TestHttpResponseTrailersFeature.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Basket.API; - -internal class TestHttpResponseTrailersFeature : IHttpResponseTrailersFeature -{ - public IHeaderDictionary Trailers { get; set; } -} diff --git a/src/Services/Basket/Basket.API/appsettings.Development.json b/src/Services/Basket/Basket.API/appsettings.Development.json index f4a3b9407..4088044ae 100644 --- a/src/Services/Basket/Basket.API/appsettings.Development.json +++ b/src/Services/Basket/Basket.API/appsettings.Development.json @@ -1,16 +1,4 @@ { - "Serilog": { - "MinimumLevel": { - "Default": "Debug", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Debug", - "System": "Warning" - } - } - }, - "IdentityUrlExternal": "http://localhost:5105", - "IdentityUrl": "http://localhost:5105", "ConnectionString": "127.0.0.1", "AzureServiceBusEnabled": false, "EventBusConnection": "localhost" diff --git a/src/Services/Basket/Basket.API/appsettings.json b/src/Services/Basket/Basket.API/appsettings.json index 295294308..3a4547555 100644 --- a/src/Services/Basket/Basket.API/appsettings.json +++ b/src/Services/Basket/Basket.API/appsettings.json @@ -1,30 +1,48 @@ { - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" - } + "Microsoft.AspNetCore": "Warning" } }, "Kestrel": { - "EndpointDefaults": { - "Protocols": "Http2" + "Endpoints": { + "Http": { + "Url": "http://localhost:5221" + }, + "gRPC": { + "Url": "http://localhost:6221", + "Protocols": "Http2" + } + } + }, + "OpenApi": { + "Endpoint": { + "Name": "Basket.API V1" + }, + "Document": { + "Description": "The Basket Service HTTP API", + "Title": "eShopOnContainers - Basket HTTP API", + "Version": "v1" + }, + "Auth": { + "ClientId": "basketswaggerui", + "AppName": "Basket Swagger UI" } }, - "SubscriptionClientName": "Basket", - "ApplicationInsights": { - "InstrumentationKey": "" + "ConnectionStrings": { + "Redis": "localhost", + "EventBus": "localhost" + }, + "Identity": { + "Audience": "basket", + "Url": "http://localhost:5223", + "Scopes": { + "basket": "Basket API" + } }, - "EventBusRetryCount": 5, - "UseVault": false, - "Vault": { - "Name": "eshop", - "ClientId": "your-client-id", - "ClientSecret": "your-client-secret" + "EventBus": { + "SubscriptionClientName": "Basket", + "RetryCount": 5 } } diff --git a/src/Services/Basket/Basket.API/azds.yaml b/src/Services/Basket/Basket.API/azds.yaml deleted file mode 100644 index 4fbbb7be4..000000000 --- a/src/Services/Basket/Basket.API/azds.yaml +++ /dev/null @@ -1,56 +0,0 @@ -kind: helm-release -apiVersion: 1.1 -build: - context: ..\..\..\.. - dockerfile: Dockerfile -install: - chart: ../../../../k8s/helm/basket-api - set: - replicaCount: 1 - image: - tag: $(tag) - pullPolicy: Never - ingress: - annotations: - kubernetes.io/ingress.class: traefik-azds - hosts: - - $(spacePrefix)eshop$(hostSuffix) - inf: - k8s: - dns: $(spacePrefix)eshop$(hostSuffix) - values: - - values.dev.yaml? - - secrets.dev.yaml? - - inf.yaml - - app.yaml -configurations: - develop: - build: - useGitIgnore: true - dockerfile: Dockerfile.develop - args: - BUILD_CONFIGURATION: ${BUILD_CONFIGURATION:-Debug} - container: - sync: - - '**/Pages/**' - - '**/Views/**' - - '**/wwwroot/**' - - '!**/*.{sln,csproj}' - command: - - dotnet - - run - - --no-restore - - --no-build - - --no-launch-profile - - -c - - ${BUILD_CONFIGURATION:-Debug} - iterate: - processesToKill: - - dotnet - - vsdbg - buildCommands: - - - dotnet - - build - - --no-restore - - -c - - ${BUILD_CONFIGURATION:-Debug} diff --git a/src/Services/Basket/Basket.API/web.config b/src/Services/Basket/Basket.API/web.config deleted file mode 100644 index a2cf1fe26..000000000 --- a/src/Services/Basket/Basket.API/web.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Services/Basket/Basket.FunctionalTests/Base/AutoAuthorizeMiddleware.cs b/src/Services/Basket/Basket.FunctionalTests/Base/AutoAuthorizeMiddleware.cs index 6343dbe68..551e3069e 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Base/AutoAuthorizeMiddleware.cs +++ b/src/Services/Basket/Basket.FunctionalTests/Base/AutoAuthorizeMiddleware.cs @@ -18,6 +18,7 @@ class AutoAuthorizeMiddleware identity.AddClaim(new Claim("sub", IDENTITY_ID)); identity.AddClaim(new Claim("unique_name", IDENTITY_ID)); identity.AddClaim(new Claim(ClaimTypes.Name, IDENTITY_ID)); + identity.AddClaim(new Claim("scope", "basket")); httpContext.User.AddIdentity(identity); diff --git a/src/Services/Basket/Basket.FunctionalTests/Base/BasketScenarioBase.cs b/src/Services/Basket/Basket.FunctionalTests/Base/BasketScenarioBase.cs index b50b58bbd..8fe4f2e60 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Base/BasketScenarioBase.cs +++ b/src/Services/Basket/Basket.FunctionalTests/Base/BasketScenarioBase.cs @@ -1,4 +1,7 @@ -namespace Basket.FunctionalTests.Base; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Hosting; + +namespace Basket.FunctionalTests.Base; public class BasketScenarioBase { @@ -6,18 +9,8 @@ public class BasketScenarioBase public TestServer CreateServer() { - var path = Assembly.GetAssembly(typeof(BasketScenarioBase)) - .Location; - - var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) - .ConfigureAppConfiguration(cb => - { - cb.AddJsonFile("appsettings.json", optional: false) - .AddEnvironmentVariables(); - }).UseStartup(); - - return new TestServer(hostBuilder); + var factory = new BasketApplication(); + return factory.Server; } public static class Get @@ -26,6 +19,11 @@ public class BasketScenarioBase { return $"{ApiUrlBase}/{id}"; } + + public static string GetBasketByCustomer(string customerId) + { + return $"{ApiUrlBase}/{customerId}"; + } } public static class Post @@ -33,4 +31,37 @@ public class BasketScenarioBase public static string Basket = $"{ApiUrlBase}/"; public static string CheckoutOrder = $"{ApiUrlBase}/checkout"; } + + private class BasketApplication : WebApplicationFactory + { + protected override IHost CreateHost(IHostBuilder builder) + { + builder.ConfigureServices(services => + { + services.AddSingleton(); + }); + + builder.ConfigureAppConfiguration(c => + { + var directory = Path.GetDirectoryName(typeof(BasketScenarioBase).Assembly.Location)!; + + c.AddJsonFile(Path.Combine(directory, "appsettings.Basket.json"), optional: false); + }); + + return base.CreateHost(builder); + } + + private class AuthStartupFilter : IStartupFilter + { + public Action Configure(Action next) + { + return app => + { + app.UseMiddleware(); + + next(app); + }; + } + } + } } diff --git a/src/Services/Basket/Basket.FunctionalTests/Base/BasketTestStartup.cs b/src/Services/Basket/Basket.FunctionalTests/Base/BasketTestStartup.cs deleted file mode 100644 index b19d825cd..000000000 --- a/src/Services/Basket/Basket.FunctionalTests/Base/BasketTestStartup.cs +++ /dev/null @@ -1,31 +0,0 @@ - - -namespace Basket.FunctionalTests.Base -{ - class BasketTestsStartup : Startup - { - public BasketTestsStartup(IConfiguration env) : base(env) - { - } - - public override IServiceProvider ConfigureServices(IServiceCollection services) - { - // Added to avoid the Authorize data annotation in test environment. - // Property "SuppressCheckForUnhandledSecurityMetadata" in appsettings.json - services.Configure(Configuration); - return base.ConfigureServices(services); - } - - protected override void ConfigureAuth(IApplicationBuilder app) - { - if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) - { - app.UseMiddleware(); - } - else - { - base.ConfigureAuth(app); - } - } - } -} diff --git a/src/Services/Basket/Basket.FunctionalTests/Base/HttpClientExtensions.cs b/src/Services/Basket/Basket.FunctionalTests/Base/HttpClientExtensions.cs deleted file mode 100644 index 45910df14..000000000 --- a/src/Services/Basket/Basket.FunctionalTests/Base/HttpClientExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Basket.FunctionalTests.Base; - -static class HttpClientExtensions -{ - public static HttpClient CreateIdempotentClient(this TestServer server) - { - var client = server.CreateClient(); - - client.DefaultRequestHeaders.Add("x-requestid", Guid.NewGuid().ToString()); - - return client; - } -} diff --git a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj index db0b48fdb..717520e67 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj +++ b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj @@ -1,31 +1,28 @@  - net6.0 + net7.0 + false false - - - - - + PreserveNewest - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs b/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs index f727b999e..4a96207a4 100644 --- a/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs +++ b/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs @@ -1,16 +1,15 @@ namespace Basket.FunctionalTests; -public class BasketScenarios - : BasketScenarioBase +public class BasketScenarios : + BasketScenarioBase { [Fact] public async Task Post_basket_and_response_ok_status_code() { using var server = CreateServer(); var content = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json"); - var response = await server.CreateClient() - .PostAsync(Post.Basket, content); - + var uri = "/api/v1/basket/"; + var response = await server.CreateClient().PostAsync(uri, content); response.EnsureSuccessStatusCode(); } @@ -20,7 +19,6 @@ public class BasketScenarios using var server = CreateServer(); var response = await server.CreateClient() .GetAsync(Get.GetBasket(1)); - response.EnsureSuccessStatusCode(); } @@ -33,9 +31,12 @@ public class BasketScenarios await server.CreateClient() .PostAsync(Post.Basket, contentBasket); - var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json"); + var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json") + { + Headers = { { "x-requestid", Guid.NewGuid().ToString() } } + }; - var response = await server.CreateIdempotentClient() + var response = await server.CreateClient() .PostAsync(Post.CheckoutOrder, contentCheckout); response.EnsureSuccessStatusCode(); diff --git a/src/Services/Basket/Basket.FunctionalTests/GlobalUsings.cs b/src/Services/Basket/Basket.FunctionalTests/GlobalUsings.cs index d3657c2d3..219e4ce57 100644 --- a/src/Services/Basket/Basket.FunctionalTests/GlobalUsings.cs +++ b/src/Services/Basket/Basket.FunctionalTests/GlobalUsings.cs @@ -1,23 +1,19 @@ -global using Basket.FunctionalTests.Base; +global using System; +global using System.Collections.Generic; +global using System.IO; +global using System.Net.Http; +global using System.Security.Claims; +global using System.Text; +global using System.Text.Json; +global using System.Threading.Tasks; +global using Basket.FunctionalTests.Base; global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Routing; global using Microsoft.AspNetCore.TestHost; -global using Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; global using Microsoft.eShopOnContainers.Services.Basket.API.Model; -global using Microsoft.eShopOnContainers.Services.Basket.API; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; global using StackExchange.Redis; -global using System.Collections.Generic; -global using System.IO; -global using System.Net.Http; -global using System.Reflection; -global using System.Security.Claims; -global using System.Text.Json; -global using System.Text; -global using System.Threading.Tasks; -global using System; global using Xunit; diff --git a/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs b/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs index 0a0cb11fa..639f36bb8 100644 --- a/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs +++ b/src/Services/Basket/Basket.FunctionalTests/RedisBasketRepositoryTests.cs @@ -1,4 +1,4 @@ - +using Basket.API.Repositories; namespace Basket.FunctionalTests { @@ -9,8 +9,8 @@ namespace Basket.FunctionalTests [Fact] public async Task UpdateBasket_return_and_add_basket() { - using var server = CreateServer(); - var redis = server.Host.Services.GetRequiredService(); + var server = CreateServer(); + var redis = server.Services.GetRequiredService(); var redisBasketRepository = BuildBasketRepository(redis); @@ -22,16 +22,13 @@ namespace Basket.FunctionalTests Assert.NotNull(basket); Assert.Single(basket.Items); - - } [Fact] public async Task Delete_Basket_return_null() { - - using var server = CreateServer(); - var redis = server.Host.Services.GetRequiredService(); + var server = CreateServer(); + var redis = server.Services.GetRequiredService(); var redisBasketRepository = BuildBasketRepository(redis); @@ -52,7 +49,7 @@ namespace Basket.FunctionalTests RedisBasketRepository BuildBasketRepository(ConnectionMultiplexer connMux) { var loggerFactory = new LoggerFactory(); - return new RedisBasketRepository(loggerFactory, connMux); + return new RedisBasketRepository(loggerFactory.CreateLogger(), connMux); } List BuildBasketItems() diff --git a/src/Services/Basket/Basket.FunctionalTests/appsettings.Basket.json b/src/Services/Basket/Basket.FunctionalTests/appsettings.Basket.json new file mode 100644 index 000000000..364a58def --- /dev/null +++ b/src/Services/Basket/Basket.FunctionalTests/appsettings.Basket.json @@ -0,0 +1,25 @@ +{ + "Logging": { + "Console": { + "IncludeScopes": false + }, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "Identity": { + "ExternalUrl": "http://localhost:5105", + "Url": "http://localhost:5105" + }, + "ConnectionStrings": { + "Redis": "127.0.0.1" + }, + "EventBus": { + "ConnectionString": "localhost", + "SubscriptionClientName": "Basket" + }, + "isTest": "true", + "SuppressCheckForUnhandledSecurityMetadata": true +} diff --git a/src/Services/Basket/Basket.FunctionalTests/appsettings.json b/src/Services/Basket/Basket.FunctionalTests/appsettings.json deleted file mode 100644 index 8b9ec4d3c..000000000 --- a/src/Services/Basket/Basket.FunctionalTests/appsettings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - }, - "IdentityUrl": "http://localhost:5105", - "IdentityUrlExternal": "http://localhost:5105", - "ConnectionString": "127.0.0.1", - "isTest": "true", - "EventBusConnection": "localhost", - "SubscriptionClientName": "Basket", - "SuppressCheckForUnhandledSecurityMetadata": true -} diff --git a/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs b/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs index 89f1c2b33..7c06a6abc 100644 --- a/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs +++ b/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs @@ -90,7 +90,7 @@ public class BasketWebApiTest } [Fact] - public async Task Doing_Checkout_Wit_Basket_Should_Publish_UserCheckoutAccepted_Integration_Event() + public async Task Doing_Checkout_With_Basket_Should_Publish_UserCheckoutAccepted_Integration_Event() { var fakeCustomerId = "1"; var fakeCustomerBasket = GetCustomerBasketFake(fakeCustomerId); diff --git a/src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs b/src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs index 4231d6a9e..44d565f39 100644 --- a/src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs +++ b/src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs @@ -79,7 +79,7 @@ public class CartControllerTest //Arrange var fakeCatalogItem = GetFakeCatalogItem(); - _basketServiceMock.Setup(x => x.AddItemToBasket(It.IsAny(), It.IsAny())) + _basketServiceMock.Setup(x => x.AddItemToBasket(It.IsAny(), It.IsAny())) .Returns(Task.FromResult(1)); //Act diff --git a/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj b/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj index 039258c2f..e39449ab9 100644 --- a/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj +++ b/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj @@ -1,23 +1,24 @@  - net6.0 + net7.0 false + false false - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Services/Basket/Basket.UnitTests/GlobalUsings.cs b/src/Services/Basket/Basket.UnitTests/GlobalUsings.cs index 6e5443b74..62eb6d21a 100644 --- a/src/Services/Basket/Basket.UnitTests/GlobalUsings.cs +++ b/src/Services/Basket/Basket.UnitTests/GlobalUsings.cs @@ -4,7 +4,6 @@ global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Mvc; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; -global using Microsoft.eShopOnContainers.Services.Basket.API.Model; global using Microsoft.Extensions.Logging; global using Moq; global using System; diff --git a/src/Services/Catalog/Catalog.API/Apis/PicApi.cs b/src/Services/Catalog/Catalog.API/Apis/PicApi.cs new file mode 100644 index 000000000..277b718a7 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Apis/PicApi.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Routing; + +namespace Catalog.API.Apis; + +public static class PicApi +{ + public static IEndpointConventionBuilder MapPicApi(this IEndpointRouteBuilder routes) + { + return routes.MapGet("api/v1/catalog/items/{catalogItemId:int}/pic", + async (int catalogItemId, CatalogContext db, IWebHostEnvironment environment) => + { + var item = await db.CatalogItems.FindAsync(catalogItemId); + + if (item is null) + { + return Results.NotFound(); + } + + var path = Path.Combine(environment.ContentRootPath, "Pics", item.PictureFileName); + + string imageFileExtension = Path.GetExtension(item.PictureFileName); + string mimetype = GetImageMimeTypeFromImageFileExtension(imageFileExtension); + + return Results.File(path, mimetype); + }) + .WithTags("Pic") + .Produces(404); + + static string GetImageMimeTypeFromImageFileExtension(string extension) => extension switch + { + ".png" => "image/png", + ".gif" => "image/gif", + ".jpg" or ".jpeg" => "image/jpeg", + ".bmp" => "image/bmp", + ".tiff" => "image/tiff", + ".wmf" => "image/wmf", + ".jp2" => "image/jp2", + ".svg" => "image/svg+xml", + _ => "application/octet-stream", + }; + } +} diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index b78ce2af3..8a4669c16 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -1,24 +1,16 @@  - net6.0 - portable - true + net7.0 Catalog.API Catalog.API aspnet-Catalog.API-20161122013618 ..\..\..\..\docker-compose.dcproj - false - true - - Always - - - PreserveNewest + PreserveNewest PreserveNewest @@ -26,68 +18,33 @@ PreserveNewest - - - - - PreserveNewest - + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - PreserveNewest - - - PreserveNewest - + + - diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 4dd1143f6..66dcd99c1 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -46,15 +46,6 @@ public class CatalogController : ControllerBase .Take(pageSize) .ToListAsync(); - /* The "awesome" fix for testing Devspaces */ - - /* - foreach (var pr in itemsOnPage) { - pr.Name = "Awesome " + pr.Name; - } - - */ - itemsOnPage = ChangeUriPlaceholder(itemsOnPage); var model = new PaginatedItemsViewModel(pageIndex, pageSize, totalItems, itemsOnPage); diff --git a/src/Services/Catalog/Catalog.API/Controllers/HomeController.cs b/src/Services/Catalog/Catalog.API/Controllers/HomeController.cs deleted file mode 100644 index cd86a2966..000000000 --- a/src/Services/Catalog/Catalog.API/Controllers/HomeController.cs +++ /dev/null @@ -1,11 +0,0 @@ -// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers; - -public class HomeController : Controller -{ - // GET: // - public IActionResult Index() - { - return new RedirectResult("~/swagger"); - } -} diff --git a/src/Services/Catalog/Catalog.API/Controllers/PicController.cs b/src/Services/Catalog/Catalog.API/Controllers/PicController.cs deleted file mode 100644 index dfb6f0d03..000000000 --- a/src/Services/Catalog/Catalog.API/Controllers/PicController.cs +++ /dev/null @@ -1,64 +0,0 @@ -// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers; - -[ApiController] -public class PicController : ControllerBase -{ - private readonly IWebHostEnvironment _env; - private readonly CatalogContext _catalogContext; - - public PicController(IWebHostEnvironment env, - CatalogContext catalogContext) - { - _env = env; - _catalogContext = catalogContext; - } - - [HttpGet] - [Route("api/v1/catalog/items/{catalogItemId:int}/pic")] - [ProducesResponseType((int)HttpStatusCode.NotFound)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - // GET: // - public async Task GetImageAsync(int catalogItemId) - { - if (catalogItemId <= 0) - { - return BadRequest(); - } - - var item = await _catalogContext.CatalogItems - .SingleOrDefaultAsync(ci => ci.Id == catalogItemId); - - if (item != null) - { - var webRoot = _env.WebRootPath; - var path = Path.Combine(webRoot, item.PictureFileName); - - string imageFileExtension = Path.GetExtension(item.PictureFileName); - string mimetype = GetImageMimeTypeFromImageFileExtension(imageFileExtension); - - var buffer = await System.IO.File.ReadAllBytesAsync(path); - - return File(buffer, mimetype); - } - - return NotFound(); - } - - private string GetImageMimeTypeFromImageFileExtension(string extension) - { - string mimetype = extension switch - { - ".png" => "image/png", - ".gif" => "image/gif", - ".jpg" or ".jpeg" => "image/jpeg", - ".bmp" => "image/bmp", - ".tiff" => "image/tiff", - ".wmf" => "image/wmf", - ".jp2" => "image/jp2", - ".svg" => "image/svg+xml", - _ => "application/octet-stream", - }; - return mimetype; - } -} diff --git a/src/Services/Catalog/Catalog.API/Dockerfile b/src/Services/Catalog/Catalog.API/Dockerfile index e491c2110..9138482c1 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile +++ b/src/Services/Catalog/Catalog.API/Dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles @@ -12,7 +12,6 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" -COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj" COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" @@ -34,6 +33,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" @@ -43,6 +43,7 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj" COPY "docker-compose.dcproj" "docker-compose.dcproj" +COPY "Directory.Packages.props" "Directory.Packages.props" COPY "NuGet.config" "NuGet.config" RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln" diff --git a/src/Services/Catalog/Catalog.API/Dockerfile.develop b/src/Services/Catalog/Catalog.API/Dockerfile.develop index 513396298..9aefa6eff 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile.develop +++ b/src/Services/Catalog/Catalog.API/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/src/Services/Catalog/Catalog.API/Extensions/Extensions.cs b/src/Services/Catalog/Catalog.API/Extensions/Extensions.cs new file mode 100644 index 000000000..490c5ede4 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Extensions/Extensions.cs @@ -0,0 +1,92 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; + +public static class Extensions +{ + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder + .AddSqlServer(_ => configuration.GetRequiredConnectionString("CatalogDB"), + name: "CatalogDB-check", + tags: new string[] { "ready" }); + + var accountName = configuration["AzureStorageAccountName"]; + var accountKey = configuration["AzureStorageAccountKey"]; + + if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) + { + hcBuilder + .AddAzureBlobStorage( + $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net", + name: "catalog-storage-check", + tags: new string[] { "ready" }); + } + + return services; + } + + public static IServiceCollection AddDbContexts(this IServiceCollection services, IConfiguration configuration) + { + static void ConfigureSqlOptions(SqlServerDbContextOptionsBuilder sqlOptions) + { + sqlOptions.MigrationsAssembly(typeof(Program).Assembly.FullName); + + // Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }; + + services.AddDbContext(options => + { + var connectionString = configuration.GetRequiredConnectionString("CatalogDB"); + + options.UseSqlServer(connectionString, ConfigureSqlOptions); + }); + + services.AddDbContext(options => + { + var connectionString = configuration.GetRequiredConnectionString("CatalogDB"); + + options.UseSqlServer(connectionString, ConfigureSqlOptions); + }); + + return services; + } + + public static IServiceCollection AddApplicationOptions(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration); + + // TODO: Move to the new problem details middleware + services.Configure(options => + { + options.InvalidModelStateResponseFactory = context => + { + var problemDetails = new ValidationProblemDetails(context.ModelState) + { + Instance = context.HttpContext.Request.Path, + Status = StatusCodes.Status400BadRequest, + Detail = "Please refer to the errors property for additional details." + }; + + return new BadRequestObjectResult(problemDetails) + { + ContentTypes = { "application/problem+json", "application/problem+xml" } + }; + }; + }); + + return services; + } + + public static IServiceCollection AddIntegrationServices(this IServiceCollection services) + { + services.AddTransient>( + sp => (DbConnection c) => new IntegrationEventLogService(c)); + + services.AddTransient(); + + return services; + } +} diff --git a/src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs b/src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs index 85fa9300c..65d72c1d2 100644 --- a/src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs +++ b/src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs @@ -13,7 +13,7 @@ public static class LinqSelectExtensions } catch (Exception ex) { - returnedValue = new SelectTryResult(element, default(TResult), ex); + returnedValue = new SelectTryResult(element, default, ex); } yield return returnedValue; } diff --git a/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs b/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs deleted file mode 100644 index 588ca7a35..000000000 --- a/src/Services/Catalog/Catalog.API/Extensions/WebHostExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Extensions; - -public static class WebHostExtensions -{ - public static bool IsInKubernetes(this IWebHost host) - { - var cfg = host.Services.GetService(); - var orchestratorType = cfg.GetValue("OrchestratorType"); - return orchestratorType?.ToUpper() == "K8S"; - } - - public static IWebHost MigrateDbContext(this IWebHost host, Action seeder) where TContext : DbContext - { - var underK8s = host.IsInKubernetes(); - - using var scope = host.Services.CreateScope(); - var services = scope.ServiceProvider; - - var logger = services.GetRequiredService>(); - - var context = services.GetService(); - - try - { - logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name); - - if (underK8s) - { - InvokeSeeder(seeder, context, services); - } - else - { - var retry = Policy.Handle() - .WaitAndRetry(new TimeSpan[] - { - TimeSpan.FromSeconds(3), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(8), - }); - - //if the sql server container is not created on run docker compose this - //migration can't fail for network related exception. The retry options for DbContext only - //apply to transient exceptions - // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) - retry.Execute(() => InvokeSeeder(seeder, context, services)); - } - - logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name); - if (underK8s) - { - throw; // Rethrow under k8s because we rely on k8s to re-run the pod - } - } - - return host; - } - - private static void InvokeSeeder(Action seeder, TContext context, IServiceProvider services) - where TContext : DbContext - { - context.Database.Migrate(); - seeder(context, services); - } -} diff --git a/src/Services/Catalog/Catalog.API/GlobalUsings.cs b/src/Services/Catalog/Catalog.API/GlobalUsings.cs index 48641cc80..e521114b2 100644 --- a/src/Services/Catalog/Catalog.API/GlobalUsings.cs +++ b/src/Services/Catalog/Catalog.API/GlobalUsings.cs @@ -1,63 +1,43 @@ -global using Azure.Core; -global using Azure.Identity; -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Extensions; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.ActionResults; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.Exceptions; -global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents; +global using System; +global using System.Collections.Generic; +global using System.Data.Common; +global using System.Data.SqlClient; +global using System.Globalization; +global using System.IO; +global using System.IO.Compression; +global using System.Linq; +global using System.Net; +global using System.Text.RegularExpressions; +global using System.Threading.Tasks; global using Grpc.Core; +global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore.Server.Kestrel.Core; -global using Microsoft.AspNetCore; -global using Microsoft.Extensions.Logging; +global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Design; global using Microsoft.EntityFrameworkCore.Metadata.Builders; -global using Microsoft.EntityFrameworkCore; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; -global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; +global using Microsoft.eShopOnContainers.Services.Catalog.API; +global using Microsoft.eShopOnContainers.Services.Catalog.API.Extensions; +global using Microsoft.eShopOnContainers.Services.Catalog.API.Grpc; global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.EntityConfigurations; -global using Microsoft.eShopOnContainers.Services.Catalog.API; +global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.Exceptions; +global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents; +global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling; global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; global using Microsoft.eShopOnContainers.Services.Catalog.API.Model; global using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Grpc; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; -global using Polly.Retry; global using Polly; -global using Serilog.Context; -global using Serilog; -global using System.Collections.Generic; -global using System.Data.Common; -global using System.Data.SqlClient; -global using System.Globalization; -global using System.IO.Compression; -global using System.IO; -global using System.Linq; -global using System.Net; -global using System.Text.RegularExpressions; -global using System.Threading.Tasks; -global using System; -global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.Filters; -global using HealthChecks.UI.Client; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Azure.Messaging.ServiceBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; -global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; -global using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling; -global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.OpenApi.Models; -global using RabbitMQ.Client; -global using System.Reflection; \ No newline at end of file +global using Polly.Retry; +global using Services.Common; +global using Catalog.API.Apis; diff --git a/src/Services/Catalog/Catalog.API/Grpc/CatalogService.cs b/src/Services/Catalog/Catalog.API/Grpc/CatalogService.cs index ebd785462..29913b086 100644 --- a/src/Services/Catalog/Catalog.API/Grpc/CatalogService.cs +++ b/src/Services/Catalog/Catalog.API/Grpc/CatalogService.cs @@ -9,7 +9,7 @@ public class CatalogService : CatalogBase private readonly CatalogContext _catalogContext; private readonly CatalogSettings _settings; private readonly ILogger _logger; - + public CatalogService(CatalogContext dbContext, IOptions settings, ILogger logger) { _settings = settings.Value; @@ -74,15 +74,6 @@ public class CatalogService : CatalogBase .Take(request.PageSize) .ToListAsync(); - /* The "awesome" fix for testing Devspaces */ - - /* - foreach (var pr in itemsOnPage) { - pr.Name = "Awesome " + pr.Name; - } - - */ - itemsOnPage = ChangeUriPlaceholder(itemsOnPage); var model = this.MapToResponse(itemsOnPage, totalItems, request.PageIndex, request.PageSize); diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs b/src/Services/Catalog/Catalog.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs deleted file mode 100644 index 53af0f0b8..000000000 --- a/src/Services/Catalog/Catalog.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.ActionResults; - -public class InternalServerErrorObjectResult : ObjectResult -{ - public InternalServerErrorObjectResult(object error) - : base(error) - { - StatusCode = StatusCodes.Status500InternalServerError; - } -} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs index cf152f40f..935039d91 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs @@ -1,6 +1,4 @@ -using Microsoft.eShopOnContainers.Services.Catalog.API.Model; - -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; public class CatalogContext : DbContext { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs index 4e3a0f6e0..472f392c9 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs @@ -1,6 +1,4 @@ -using Microsoft.eShopOnContainers.Services.Catalog.API.Model; - -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; public class CatalogContextSeed { @@ -62,14 +60,14 @@ public class CatalogContextSeed } catch (Exception ex) { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); + logger.LogError(ex, "Error reading CSV headers"); return GetPreconfiguredCatalogBrands(); } return File.ReadAllLines(csvFileCatalogBrands) .Skip(1) // skip header row - .SelectTry(x => CreateCatalogBrand(x)) - .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) + .SelectTry(CreateCatalogBrand) + .OnCaughtException(ex => { logger.LogError(ex, "Error creating brand while seeding database"); return null; }) .Where(x => x != null); } @@ -77,9 +75,9 @@ public class CatalogContextSeed { brand = brand.Trim('"').Trim(); - if (String.IsNullOrEmpty(brand)) + if (string.IsNullOrEmpty(brand)) { - throw new Exception("catalog Brand Name is empty"); + throw new Exception("Catalog Brand Name is empty"); } return new CatalogBrand @@ -117,14 +115,14 @@ public class CatalogContextSeed } catch (Exception ex) { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); + logger.LogError(ex, "Error reading CSV headers"); return GetPreconfiguredCatalogTypes(); } return File.ReadAllLines(csvFileCatalogTypes) .Skip(1) // skip header row .SelectTry(x => CreateCatalogType(x)) - .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "Error creating catalog type while seeding database"); return null; }) .Where(x => x != null); } @@ -132,7 +130,7 @@ public class CatalogContextSeed { type = type.Trim('"').Trim(); - if (String.IsNullOrEmpty(type)) + if (string.IsNullOrEmpty(type)) { throw new Exception("catalog Type Name is empty"); } @@ -172,7 +170,7 @@ public class CatalogContextSeed } catch (Exception ex) { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); + logger.LogError(ex, "Error reading CSV headers"); return GetPreconfiguredItems(); } @@ -183,11 +181,11 @@ public class CatalogContextSeed .Skip(1) // skip header row .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)")) .SelectTry(column => CreateCatalogItem(column, csvheaders, catalogTypeIdLookup, catalogBrandIdLookup)) - .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "Error creating catalog item while seeding database"); return null; }) .Where(x => x != null); } - private CatalogItem CreateCatalogItem(string[] column, string[] headers, Dictionary catalogTypeIdLookup, Dictionary catalogBrandIdLookup) + private CatalogItem CreateCatalogItem(string[] column, string[] headers, Dictionary catalogTypeIdLookup, Dictionary catalogBrandIdLookup) { if (column.Count() != headers.Count()) { @@ -207,7 +205,7 @@ public class CatalogContextSeed } string priceString = column[Array.IndexOf(headers, "price")].Trim('"').Trim(); - if (!Decimal.TryParse(priceString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out Decimal price)) + if (!decimal.TryParse(priceString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out decimal price)) { throw new Exception($"price={priceString}is not a valid decimal number"); } @@ -226,7 +224,7 @@ public class CatalogContextSeed if (availableStockIndex != -1) { string availableStockString = column[availableStockIndex].Trim('"').Trim(); - if (!String.IsNullOrEmpty(availableStockString)) + if (!string.IsNullOrEmpty(availableStockString)) { if (int.TryParse(availableStockString, out int availableStock)) { @@ -243,7 +241,7 @@ public class CatalogContextSeed if (restockThresholdIndex != -1) { string restockThresholdString = column[restockThresholdIndex].Trim('"').Trim(); - if (!String.IsNullOrEmpty(restockThresholdString)) + if (!string.IsNullOrEmpty(restockThresholdString)) { if (int.TryParse(restockThresholdString, out int restockThreshold)) { @@ -260,7 +258,7 @@ public class CatalogContextSeed if (maxStockThresholdIndex != -1) { string maxStockThresholdString = column[maxStockThresholdIndex].Trim('"').Trim(); - if (!String.IsNullOrEmpty(maxStockThresholdString)) + if (!string.IsNullOrEmpty(maxStockThresholdString)) { if (int.TryParse(maxStockThresholdString, out int maxStockThreshold)) { @@ -277,7 +275,7 @@ public class CatalogContextSeed if (onReorderIndex != -1) { string onReorderString = column[onReorderIndex].Trim('"').Trim(); - if (!String.IsNullOrEmpty(onReorderString)) + if (!string.IsNullOrEmpty(onReorderString)) { if (bool.TryParse(onReorderString, out bool onReorder)) { @@ -318,7 +316,7 @@ public class CatalogContextSeed if (csvheaders.Count() < requiredHeaders.Count()) { - throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is bigger then csv header count '{csvheaders.Count()}' "); + throw new Exception($"requiredHeader count '{requiredHeaders.Count()}' is bigger then csv header count '{csvheaders.Count()}' "); } if (optionalHeaders != null) @@ -363,7 +361,7 @@ public class CatalogContextSeed sleepDurationProvider: retry => TimeSpan.FromSeconds(5), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); + logger.LogWarning(exception, "[{prefix}] Error seeding database (attempt {retry} of {retries})", prefix, retry, retries); } ); } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170314083211_AddEventTable.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170314083211_AddEventTable.cs index 953797a75..fb500fc6e 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170314083211_AddEventTable.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170314083211_AddEventTable.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Catalog.API.Infrastructure.Migrations { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316012921_RefactoringToIntegrationEventLog.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316012921_RefactoringToIntegrationEventLog.cs index a6c69efa3..a86450fc8 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316012921_RefactoringToIntegrationEventLog.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316012921_RefactoringToIntegrationEventLog.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Catalog.API.Infrastructure.Migrations { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.cs index 392580d00..aa8b0d765 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Catalog.API.Infrastructure.Migrations { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs index 455e87ec9..bc5fecc6e 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs @@ -1,7 +1,5 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; namespace Catalog.API.Infrastructure.Migrations { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs deleted file mode 100644 index 66ee35e7b..000000000 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.Filters; - -public class HttpGlobalExceptionFilter : IExceptionFilter -{ - private readonly IWebHostEnvironment env; - private readonly ILogger logger; - - public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger logger) - { - this.env = env; - this.logger = logger; - } - - public void OnException(ExceptionContext context) - { - logger.LogError(new EventId(context.Exception.HResult), - context.Exception, - context.Exception.Message); - - if (context.Exception.GetType() == typeof(CatalogDomainException)) - { - var problemDetails = new ValidationProblemDetails() - { - Instance = context.HttpContext.Request.Path, - Status = StatusCodes.Status400BadRequest, - Detail = "Please refer to the errors property for additional details." - }; - - problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() }); - - context.Result = new BadRequestObjectResult(problemDetails); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } - else - { - var json = new JsonErrorResponse - { - Messages = new[] { "An error ocurred." } - }; - - if (env.IsDevelopment()) - { - json.DeveloperMessage = context.Exception; - } - - context.Result = new InternalServerErrorObjectResult(json); - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - context.ExceptionHandled = true; - } - - private class JsonErrorResponse - { - public string[] Messages { get; set; } - - public object DeveloperMessage { get; set; } - } -} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.cs b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.cs index 5f7ea0c73..7b8e18395 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore.Migrations; -using System; namespace Catalog.API.Migrations { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextDesignTimeFactory.cs b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextDesignTimeFactory.cs index 3841e3a20..3195d9714 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextDesignTimeFactory.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextDesignTimeFactory.cs @@ -1,8 +1,4 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; - -namespace Catalog.API.Infrastructure.IntegrationEventMigrations +namespace Catalog.API.Infrastructure.IntegrationEventMigrations { public class IntegrationEventLogContextDesignTimeFactory : IDesignTimeDbContextFactory { diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs index 282bea5b3..44c7b8c75 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs @@ -26,7 +26,7 @@ public class CatalogIntegrationEventService : ICatalogIntegrationEventService, I { try { - _logger.LogInformation("----- Publishing integration event: {IntegrationEventId_published} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt); + _logger.LogInformation("Publishing integration event: {IntegrationEventId_published} - ({@IntegrationEvent})", evt.Id, evt); await _eventLogService.MarkEventAsInProgressAsync(evt.Id); _eventBus.Publish(evt); @@ -34,14 +34,14 @@ public class CatalogIntegrationEventService : ICatalogIntegrationEventService, I } catch (Exception ex) { - _logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt); + _logger.LogError(ex, "Error Publishing integration event: {IntegrationEventId} - ({@IntegrationEvent})", evt.Id, evt); await _eventLogService.MarkEventAsFailedAsync(evt.Id); } } public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt) { - _logger.LogInformation("----- CatalogIntegrationEventService - Saving changes and integrationEvent: {IntegrationEventId}", evt.Id); + _logger.LogInformation("CatalogIntegrationEventService - Saving changes and integrationEvent: {IntegrationEventId}", evt.Id); //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs index 35c523c39..6ad0a3867 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs @@ -1,5 +1,5 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling; - + public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : IIntegrationEventHandler { @@ -19,9 +19,9 @@ public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); var confirmedOrderStockItems = new List(); diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs index 8882e78a6..48e7fe571 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs @@ -16,9 +16,9 @@ public class OrderStatusChangedToPaidIntegrationEventHandler : public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event) { - using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + using (_logger.BeginScope(new List> { new ("IntegrationEventContext", @event.Id) })) { - _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + _logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); //we're not blocking stock/inventory foreach (var orderStockItem in @event.OrderStockItems) diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index e5ef82aff..695ea9197 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -1,106 +1,43 @@ -var configuration = GetConfiguration(); +var builder = WebApplication.CreateBuilder(args); -Log.Logger = CreateSerilogLogger(configuration); +builder.AddServiceDefaults(); -try -{ - Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); - var host = CreateHostBuilder(configuration, args); +builder.Services.AddGrpc(); +builder.Services.AddControllers(); - Log.Information("Applying migrations ({ApplicationContext})...", Program.AppName); - host.MigrateDbContext((context, services) => - { - var env = services.GetService(); - var settings = services.GetService>(); - var logger = services.GetService>(); +// Application specific services +builder.Services.AddHealthChecks(builder.Configuration); +builder.Services.AddDbContexts(builder.Configuration); +builder.Services.AddApplicationOptions(builder.Configuration); +builder.Services.AddIntegrationServices(); - new CatalogContextSeed().SeedAsync(context, env, settings, logger).Wait(); - }) - .MigrateDbContext((_, __) => { }); +builder.Services.AddTransient(); +builder.Services.AddTransient(); - Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); - host.Run(); +var app = builder.Build(); - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); - return 1; -} -finally -{ - Log.CloseAndFlush(); -} +app.UseServiceDefaults(); -IWebHost CreateHostBuilder(IConfiguration configuration, string[] args) => - WebHost.CreateDefaultBuilder(args) - .ConfigureAppConfiguration(x => x.AddConfiguration(configuration)) - .CaptureStartupErrors(false) - .ConfigureKestrel(options => - { - var ports = GetDefinedPorts(configuration); - options.Listen(IPAddress.Any, ports.httpPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); - options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http2; - }); +app.MapPicApi(); +app.MapControllers(); +app.MapGrpcService(); - }) - .UseStartup() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseWebRoot("Pics") - .UseSerilog() - .Build(); +var eventBus = app.Services.GetRequiredService(); -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", Program.AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) - .ReadFrom.Configuration(configuration) - .CreateLogger(); -} +eventBus.Subscribe(); +eventBus.Subscribe(); -(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) +// REVIEW: This is done fore development east but shouldn't be here in production +using (var scope = app.Services.CreateScope()) { - var grpcPort = config.GetValue("GRPC_PORT", 81); - var port = config.GetValue("PORT", 80); - return (port, grpcPort); + var context = scope.ServiceProvider.GetRequiredService(); + var settings = app.Services.GetService>(); + var logger = app.Services.GetService>(); + await context.Database.MigrateAsync(); + + await new CatalogContextSeed().SeedAsync(context, app.Environment, settings, logger); + var integEventContext = scope.ServiceProvider.GetRequiredService(); + await integEventContext.Database.MigrateAsync(); } -IConfiguration GetConfiguration() -{ - var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddEnvironmentVariables(); - - var config = builder.Build(); - - if (config.GetValue("UseVault", false)) - { - TokenCredential credential = new ClientSecretCredential( - config["Vault:TenantId"], - config["Vault:ClientId"], - config["Vault:ClientSecret"]); - //builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential); - } - - return builder.Build(); -} - -public partial class Program -{ - public static string Namespace = typeof(Startup).Namespace; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); -} \ No newline at end of file +await app.RunAsync(); diff --git a/src/Services/Catalog/Catalog.API/Properties/launchSettings.json b/src/Services/Catalog/Catalog.API/Properties/launchSettings.json index a29630269..1b56eefed 100644 --- a/src/Services/Catalog/Catalog.API/Properties/launchSettings.json +++ b/src/Services/Catalog/Catalog.API/Properties/launchSettings.json @@ -1,39 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:57424/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "/swagger", - "environmentVariables": { - "ConnectionString": "server=localhost,5433;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", - "Serilog:LogstashgUrl": "http://locahost:8080", - "ASPNETCORE_ENVIRONMENT": "Development", - "EventBusConnection": "localhost", - "Serilog:SeqServerUrl": "http://locahost:5340" - } - }, - "Microsoft.eShopOnContainers.Services.Catalog.API": { + "Catalog.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:55101/", + "applicationUrl": "http://localhost:5222/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - "Azure Dev Spaces": { - "commandName": "AzureDevSpaces", - "launchBrowser": true, - "resourceGroup": "edu-devspaces3", - "aksName": "edu-devspaces3", - "subscriptionId": "e3035ac1-c06c-4daf-8939-57b3c5f1f759" } } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.json b/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.json new file mode 100644 index 000000000..62b84ead2 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "rabbitmq1": { + "type": "rabbitmq", + "connectionId": "ConnectionStrings:EventBus", + "dynamicId": null + }, + "mssql1": { + "type": "mssql", + "connectionId": "ConnectionStrings:CatalogDB", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.local.json b/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.local.json new file mode 100644 index 000000000..9dcbd89ce --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Properties/serviceDependencies.local.json @@ -0,0 +1,23 @@ +{ + "dependencies": { + "rabbitmq1": { + "containerPorts": "5672:5672,15672:15672", + "secretStore": "LocalSecretsFile", + "containerName": "rabbitmq", + "containerImage": "rabbitmq:3-management-alpine", + "type": "rabbitmq.container", + "connectionId": "ConnectionStrings:EventBus", + "dynamicId": null + }, + "mssql1": { + "serviceConnectorResourceId": "", + "containerPorts": "1434:1433", + "secretStore": "LocalSecretsFile", + "containerName": "catalog-mssql", + "containerImage": "mcr.microsoft.com/mssql/server:2019-latest", + "type": "mssql.container", + "connectionId": "ConnectionStrings:CatalogDB", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs deleted file mode 100644 index f7b46cb6f..000000000 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ /dev/null @@ -1,333 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Catalog.API; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public IServiceProvider ConfigureServices(IServiceCollection services) - { - services.AddAppInsight(Configuration) - .AddGrpc().Services - .AddCustomMVC(Configuration) - .AddCustomDbContext(Configuration) - .AddCustomOptions(Configuration) - .AddIntegrationServices(Configuration) - .AddEventBus(Configuration) - .AddSwagger(Configuration) - .AddCustomHealthCheck(Configuration); - - var container = new ContainerBuilder(); - container.Populate(services); - - return new AutofacServiceProvider(container.Build()); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - //Configure logs - - //loggerFactory.AddAzureWebAppDiagnostics(); - //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); - - var pathBase = Configuration["PATH_BASE"]; - - if (!string.IsNullOrEmpty(pathBase)) - { - loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - app.UseSwagger() - .UseSwaggerUI(c => - { - c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Catalog.API V1"); - }); - - app.UseRouting(); - app.UseCors("CorsPolicy"); - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapGet("/_proto/", async ctx => - { - ctx.Response.ContentType = "text/plain"; - using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "catalog.proto"), FileMode.Open, FileAccess.Read); - using var sr = new StreamReader(fs); - while (!sr.EndOfStream) - { - var line = await sr.ReadLineAsync(); - if (line != "/* >>" || line != "<< */") - { - await ctx.Response.WriteAsync(line); - } - } - }); - endpoints.MapGrpcService(); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - }); - - ConfigureEventBus(app); - } - - protected virtual void ConfigureEventBus(IApplicationBuilder app) - { - var eventBus = app.ApplicationServices.GetRequiredService(); - eventBus.Subscribe(); - eventBus.Subscribe(); - } -} - -public static class CustomExtensionMethods -{ - public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration) - { - services.AddApplicationInsightsTelemetry(configuration); - services.AddApplicationInsightsKubernetesEnricher(); - - return services; - } - - public static IServiceCollection AddCustomMVC(this IServiceCollection services, IConfiguration configuration) - { - services.AddControllers(options => - { - options.Filters.Add(typeof(HttpGlobalExceptionFilter)); - }) - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - - services.AddCors(options => - { - options.AddPolicy("CorsPolicy", - builder => builder - .SetIsOriginAllowed((host) => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials()); - }); - - return services; - } - - public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) - { - var accountName = configuration.GetValue("AzureStorageAccountName"); - var accountKey = configuration.GetValue("AzureStorageAccountKey"); - - var hcBuilder = services.AddHealthChecks(); - - hcBuilder - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddSqlServer( - configuration["ConnectionString"], - name: "CatalogDB-check", - tags: new string[] { "catalogdb" }); - - if (!string.IsNullOrEmpty(accountName) && !string.IsNullOrEmpty(accountKey)) - { - hcBuilder - .AddAzureBlobStorage( - $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net", - name: "catalog-storage-check", - tags: new string[] { "catalogstorage" }); - } - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - hcBuilder - .AddAzureServiceBusTopic( - configuration["EventBusConnection"], - topicName: "eshop_event_bus", - name: "catalog-servicebus-check", - tags: new string[] { "servicebus" }); - } - else - { - hcBuilder - .AddRabbitMQ( - $"amqp://{configuration["EventBusConnection"]}", - name: "catalog-rabbitmqbus-check", - tags: new string[] { "rabbitmqbus" }); - } - - return services; - } - - public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) - { - services.AddEntityFrameworkSqlServer() - .AddDbContext(options => - { - options.UseSqlServer(configuration["ConnectionString"], - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); - }); - - services.AddDbContext(options => - { - options.UseSqlServer(configuration["ConnectionString"], - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); - }); - - return services; - } - - public static IServiceCollection AddCustomOptions(this IServiceCollection services, IConfiguration configuration) - { - services.Configure(configuration); - services.Configure(options => - { - options.InvalidModelStateResponseFactory = context => - { - var problemDetails = new ValidationProblemDetails(context.ModelState) - { - Instance = context.HttpContext.Request.Path, - Status = StatusCodes.Status400BadRequest, - Detail = "Please refer to the errors property for additional details." - }; - - return new BadRequestObjectResult(problemDetails) - { - ContentTypes = { "application/problem+json", "application/problem+xml" } - }; - }; - }); - - return services; - } - - public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration) - { - services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "eShopOnContainers - Catalog HTTP API", - Version = "v1", - Description = "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample" - }); - }); - - return services; - - } - - public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration) - { - services.AddTransient>( - sp => (DbConnection c) => new IntegrationEventLogService(c)); - - services.AddTransient(); - - if (configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var settings = sp.GetRequiredService>().Value; - var serviceBusConnection = settings.EventBusConnection; - - return new DefaultServiceBusPersisterConnection(serviceBusConnection); - }); - } - else - { - services.AddSingleton(sp => - { - var settings = sp.GetRequiredService>().Value; - var logger = sp.GetRequiredService>(); - - var factory = new ConnectionFactory() - { - HostName = configuration["EventBusConnection"], - DispatchConsumersAsync = true - }; - - if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) - { - factory.UserName = configuration["EventBusUserName"]; - } - - if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) - { - factory.Password = configuration["EventBusPassword"]; - } - - var retryCount = 5; - if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(configuration["EventBusRetryCount"]); - } - - return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); - }); - } - - return services; - } - - public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) - { - if (configuration.GetValue("AzureServiceBusEnabled")) - { - services.AddSingleton(sp => - { - var serviceBusPersisterConnection = sp.GetRequiredService(); - var iLifetimeScope = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubcriptionsManager = sp.GetRequiredService(); - string subscriptionName = configuration["SubscriptionClientName"]; - - return new EventBusServiceBus(serviceBusPersisterConnection, logger, - eventBusSubcriptionsManager, iLifetimeScope, subscriptionName); - }); - - } - else - { - services.AddSingleton(sp => - { - var subscriptionClientName = configuration["SubscriptionClientName"]; - var rabbitMQPersistentConnection = sp.GetRequiredService(); - var iLifetimeScope = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); - var eventBusSubcriptionsManager = sp.GetRequiredService(); - - var retryCount = 5; - if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) - { - retryCount = int.Parse(configuration["EventBusRetryCount"]); - } - - return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); - }); - } - - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - - return services; - } -} \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/appsettings.Development.json b/src/Services/Catalog/Catalog.API/appsettings.Development.json index 1d5574f63..6e2811bf0 100644 --- a/src/Services/Catalog/Catalog.API/appsettings.Development.json +++ b/src/Services/Catalog/Catalog.API/appsettings.Development.json @@ -1,15 +1,6 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", - "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", - "Serilog": { - "MinimumLevel": { - "Default": "Debug", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Debug", - "System": "Warning" - } - } + "ConnectionStrings": { + "CatalogDB": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;Encrypt=false" }, - "EventBusConnection": "localhost" + "PicBaseUrl": "http://localhost:5222/api/v1/catalog/items/[0]/pic/" } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/appsettings.json b/src/Services/Catalog/Catalog.API/appsettings.json index f8342fe8d..3ddd8ff25 100644 --- a/src/Services/Catalog/Catalog.API/appsettings.json +++ b/src/Services/Catalog/Catalog.API/appsettings.json @@ -1,30 +1,43 @@ { - "UseCustomizationData": false, - "Serilog": { - "SeqServerUrl": null, - "LogstashgUrl": null, - "MinimumLevel": { + "Logging": { + "LogLevel": { "Default": "Information", - "Override": { - "Microsoft": "Warning", - "Microsoft.eShopOnContainers": "Information", - "System": "Warning" + "Microsoft.AspNetCore": "Warning" + } + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5222" + }, + "gRPC": { + "Url": "http://localhost:6222", + "Protocols": "Http2" } } }, - "AzureServiceBusEnabled": false, - "AzureStorageEnabled": false, - "SubscriptionClientName": "Catalog", + "OpenApi": { + "Endpoint": { + "Name": "Catalog.API V1" + }, + "Document": { + "Description": "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample", + "Title": "eShopOnContainers - Catalog HTTP API", + "Version": "v1" + } + }, + "ConnectionStrings": { + "EventBus": "localhost" + }, + "EventBus": { + "SubscriptionClientName": "Catalog", + "RetryCount": 5 + }, "ApplicationInsights": { "InstrumentationKey": "" }, - "EventBusRetryCount": 5, - "UseVault": false, - "Vault": { - "Name": "eshop", - "ClientId": "your-client-id", - "ClientSecret": "your-client-secret" - } - + "UseCustomizationData": false, + "AzureServiceBusEnabled": false, + "AzureStorageEnabled": false } - + diff --git a/src/Services/Catalog/Catalog.API/azds.yaml b/src/Services/Catalog/Catalog.API/azds.yaml deleted file mode 100644 index 9f98a3793..000000000 --- a/src/Services/Catalog/Catalog.API/azds.yaml +++ /dev/null @@ -1,54 +0,0 @@ -kind: helm-release -apiVersion: 1.1 -build: - context: ..\..\..\.. - dockerfile: Dockerfile -install: - chart: ../../../../k8s/helm/catalog-api - set: - image: - tag: $(tag) - pullPolicy: Never - ingress: - annotations: - kubernetes.io/ingress.class: traefik-azds - hosts: - - $(spacePrefix)eshop$(hostSuffix) - inf: - k8s: - dns: $(spacePrefix)eshop$(hostSuffix) - values: - - values.dev.yaml? - - secrets.dev.yaml? - - inf.yaml - - app.yaml -configurations: - develop: - build: - useGitIgnore: true - dockerfile: Dockerfile.develop - container: - syncTarget: /src - sync: - - '**/Pages/**' - - '**/Views/**' - - '**/wwwroot/**' - - '!**/*.{sln,csproj}' - command: - - dotnet - - run - - --no-restore - - --no-build - - --no-launch-profile - - -c - - ${Configuration:-Debug} - iterate: - processesToKill: - - dotnet - - vsdbg - buildCommands: - - - dotnet - - build - - --no-restore - - -c - - ${Configuration:-Debug} diff --git a/src/Services/Catalog/Catalog.API/web.config b/src/Services/Catalog/Catalog.API/web.config deleted file mode 100644 index 6da4550d8..000000000 --- a/src/Services/Catalog/Catalog.API/web.config +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj index cb8ec5630..37f9cf89b 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj +++ b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj @@ -1,13 +1,13 @@  - net6.0 - + net7.0 + false false - + @@ -15,7 +15,7 @@ - + Always @@ -33,14 +33,14 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs b/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs index 793d170ae..64296dd0a 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs +++ b/src/Services/Catalog/Catalog.FunctionalTests/CatalogScenarioBase.cs @@ -1,38 +1,29 @@ +using System; +using Microsoft.AspNetCore.Mvc.Testing; + namespace Catalog.FunctionalTests; -public class CatalogScenariosBase +public class CatalogScenariosBase { - public TestServer CreateServer() + private class CatalogApplication : WebApplicationFactory { - var path = Assembly.GetAssembly(typeof(CatalogScenariosBase)) - .Location; - - var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) - .ConfigureAppConfiguration(cb => + protected override IHost CreateHost(IHostBuilder builder) + { + builder.ConfigureAppConfiguration(c => { - cb.AddJsonFile("appsettings.json", optional: false) - .AddEnvironmentVariables(); - }) - .UseStartup(); + var directory = Path.GetDirectoryName(typeof(CatalogScenariosBase).Assembly.Location)!; + c.AddJsonFile(Path.Combine(directory, "appsettings.Catalog.json"), optional: false); + }); - var testServer = new TestServer(hostBuilder); - - testServer.Host - .MigrateDbContext((context, services) => - { - var env = services.GetService(); - var settings = services.GetService>(); - var logger = services.GetService>(); - - new CatalogContextSeed() - .SeedAsync(context, env, settings, logger) - .Wait(); - }) - .MigrateDbContext((_, __) => { }); + return base.CreateHost(builder); + } + } - return testServer; + public TestServer CreateServer() + { + var factory = new CatalogApplication(); + return factory.Server; } public static class Get @@ -75,4 +66,9 @@ public class CatalogScenariosBase return $"?pageIndex={pageIndex}&pageSize={pageCount}"; } } + + public static class Put + { + public static string UpdateCatalogProduct = "api/v1/catalog/items"; + } } diff --git a/src/Services/Catalog/Catalog.FunctionalTests/appsettings.Catalog.json b/src/Services/Catalog/Catalog.FunctionalTests/appsettings.Catalog.json new file mode 100644 index 000000000..6cd2cb192 --- /dev/null +++ b/src/Services/Catalog/Catalog.FunctionalTests/appsettings.Catalog.json @@ -0,0 +1,13 @@ +{ + "ExternalCatalogBaseUrl": "http://localhost:5101", + "isTest": "true", + "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", + + "ConnectionStrings": { + "CatalogDB": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word;Encrypt=False;TrustServerCertificate=true" + }, + + "EventBus": { + "SubscriptionClientName": "Catalog" + } +} diff --git a/src/Services/Catalog/Catalog.FunctionalTests/appsettings.json b/src/Services/Catalog/Catalog.FunctionalTests/appsettings.json deleted file mode 100644 index 0cd61e36b..000000000 --- a/src/Services/Catalog/Catalog.FunctionalTests/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", - "ExternalCatalogBaseUrl": "http://localhost:5101", - "IdentityUrl": "http://localhost:5105", - "isTest": "true", - "EventBusConnection": "localhost", - "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", - "SubscriptionClientName": "Catalog" -} diff --git a/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj b/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj index 513174e1d..a0fbbf2f1 100644 --- a/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj +++ b/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj @@ -1,21 +1,22 @@  - net6.0 + net7.0 false + false false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Services/Identity/Identity.API/AppSettings.cs b/src/Services/Identity/Identity.API/AppSettings.cs deleted file mode 100644 index 1f45763fe..000000000 --- a/src/Services/Identity/Identity.API/AppSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API -{ - public class AppSettings - { - public string MvcClient { get; set; } - - public bool UseCustomizationData { get; set; } - } -} diff --git a/src/Services/Identity/Identity.API/Certificate/Certificate.cs b/src/Services/Identity/Identity.API/Certificate/Certificate.cs deleted file mode 100644 index 91aa74a6c..000000000 --- a/src/Services/Identity/Identity.API/Certificate/Certificate.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Certificates -{ - static class Certificate - { - public static X509Certificate2 Get() - { - var assembly = typeof(Certificate).GetTypeInfo().Assembly; - var names = assembly.GetManifestResourceNames(); - - /*********************************************************************************************** - * Please note that here we are using a local certificate only for testing purposes. In a - * real environment the certificate should be created and stored in a secure way, which is out - * of the scope of this project. - **********************************************************************************************/ - using var stream = assembly.GetManifestResourceStream("Identity.API.Certificate.idsrv3test.pfx"); - return new X509Certificate2(ReadStream(stream), "idsrv3test"); - } - - private static byte[] ReadStream(Stream input) - { - byte[] buffer = new byte[16 * 1024]; - using MemoryStream ms = new MemoryStream(); - int read; - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - { - ms.Write(buffer, 0, read); - } - return ms.ToArray(); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Certificate/idsrv3test.pfx b/src/Services/Identity/Identity.API/Certificate/idsrv3test.pfx deleted file mode 100644 index 0247dea03..000000000 Binary files a/src/Services/Identity/Identity.API/Certificate/idsrv3test.pfx and /dev/null differ diff --git a/src/Services/Identity/Identity.API/Configuration/Config.cs b/src/Services/Identity/Identity.API/Configuration/Config.cs index 6d5393bde..066cbdf2a 100644 --- a/src/Services/Identity/Identity.API/Configuration/Config.cs +++ b/src/Services/Identity/Identity.API/Configuration/Config.cs @@ -1,12 +1,10 @@ -using IdentityServer4.Models; - -namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration +namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration { public class Config { // ApiResources define the apis in your system public static IEnumerable GetApis() - { + { return new List { new ApiResource("orders", "Orders Service"), @@ -18,6 +16,21 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration }; } + // ApiScope is used to protect the API + //The effect is the same as that of API resources in IdentityServer 3.x + public static IEnumerable GetApiScopes() + { + return new List + { + new ApiScope("orders", "Orders Service"), + new ApiScope("basket", "Basket Service"), + new ApiScope("mobileshoppingagg", "Mobile Shopping Aggregator"), + new ApiScope("webshoppingagg", "Web Shopping Aggregator"), + new ApiScope("orders.signalrhub", "Ordering Signalr Hub"), + new ApiScope("webhooks", "Webhooks registration Service"), + }; + } + // Identity resources are data like user ID, name, or email address of a user // see: http://docs.identityserver.io/en/release/configuration/resources.html public static IEnumerable GetResources() @@ -30,7 +43,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration } // client want to access resources (aka scopes) - public static IEnumerable GetClients(Dictionary clientsUrl) + public static IEnumerable GetClients(IConfiguration configuration) { return new List { @@ -41,10 +54,10 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration ClientName = "eShop SPA OpenId Client", AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["Spa"]}/" }, + RedirectUris = { $"{configuration["SpaClient"]}/" }, RequireConsent = false, - PostLogoutRedirectUris = { $"{clientsUrl["Spa"]}/" }, - AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" }, + PostLogoutRedirectUris = { $"{configuration["SpaClient"]}/" }, + AllowedCorsOrigins = { $"{configuration["SpaClient"]}" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, @@ -63,13 +76,13 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Hybrid, //Used to retrieve the access token on the back channel. ClientSecrets = - { + { new Secret("secret".Sha256()) }, - RedirectUris = { clientsUrl["Xamarin"] }, + RedirectUris = { configuration["XamarinCallback"] }, RequireConsent = false, RequirePkce = true, - PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" }, + PostLogoutRedirectUris = { $"{configuration["XamarinCallback"]}/Account/Redirecting" }, //AllowedCorsOrigins = { "http://eshopxamarin" }, AllowedScopes = new List { @@ -91,22 +104,23 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration ClientName = "MVC Client", ClientSecrets = new List { - + new Secret("secret".Sha256()) }, - ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client - AllowedGrantTypes = GrantTypes.Hybrid, + ClientUri = $"{configuration["MvcClient"]}", // public uri of the client + AllowedGrantTypes = GrantTypes.Code, AllowAccessTokensViaBrowser = false, RequireConsent = false, AllowOfflineAccess = true, AlwaysIncludeUserClaimsInIdToken = true, + RequirePkce = false, RedirectUris = new List { - $"{clientsUrl["Mvc"]}/signin-oidc" + $"{configuration["MvcClient"]}/signin-oidc" }, PostLogoutRedirectUris = new List { - $"{clientsUrl["Mvc"]}/signout-callback-oidc" + $"{configuration["MvcClient"]}/signout-callback-oidc" }, AllowedScopes = new List { @@ -130,19 +144,19 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration { new Secret("secret".Sha256()) }, - ClientUri = $"{clientsUrl["WebhooksWeb"]}", // public uri of the client - AllowedGrantTypes = GrantTypes.Hybrid, + ClientUri = $"{configuration["WebhooksWebClient"]}", // public uri of the client + AllowedGrantTypes = GrantTypes.Code, AllowAccessTokensViaBrowser = false, RequireConsent = false, AllowOfflineAccess = true, AlwaysIncludeUserClaimsInIdToken = true, RedirectUris = new List { - $"{clientsUrl["WebhooksWeb"]}/signin-oidc" + $"{configuration["WebhooksWebClient"]}/signin-oidc" }, PostLogoutRedirectUris = new List { - $"{clientsUrl["WebhooksWeb"]}/signout-callback-oidc" + $"{configuration["WebhooksWebClient"]}/signout-callback-oidc" }, AllowedScopes = new List { @@ -162,18 +176,18 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration { new Secret("secret".Sha256()) }, - ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client - AllowedGrantTypes = GrantTypes.Hybrid, + ClientUri = $"{configuration["Mvc"]}", // public uri of the client + AllowedGrantTypes = GrantTypes.Code, AllowAccessTokensViaBrowser = true, RequireConsent = false, AllowOfflineAccess = true, RedirectUris = new List { - $"{clientsUrl["Mvc"]}/signin-oidc" + $"{configuration["MvcClient"]}/signin-oidc" }, PostLogoutRedirectUris = new List { - $"{clientsUrl["Mvc"]}/signout-callback-oidc" + $"{configuration["MvcClient"]}/signout-callback-oidc" }, AllowedScopes = new List { @@ -193,8 +207,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/" }, + RedirectUris = { $"{configuration["BasketApiClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["BasketApiClient"]}/swagger/" }, AllowedScopes = { @@ -208,8 +222,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/" }, + RedirectUris = { $"{configuration["OrderingApiClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["OrderingApiClient"]}/swagger/" }, AllowedScopes = { @@ -223,8 +237,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["MobileShoppingAgg"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["MobileShoppingAgg"]}/swagger/" }, + RedirectUris = { $"{configuration["MobileShoppingAggClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["MobileShoppingAggClient"]}/swagger/" }, AllowedScopes = { @@ -238,8 +252,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/" }, + RedirectUris = { $"{configuration["WebShoppingAggClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["WebShoppingAggClient"]}/swagger/" }, AllowedScopes = { @@ -254,8 +268,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, - RedirectUris = { $"{clientsUrl["WebhooksApi"]}/swagger/oauth2-redirect.html" }, - PostLogoutRedirectUris = { $"{clientsUrl["WebhooksApi"]}/swagger/" }, + RedirectUris = { $"{configuration["WebhooksApiClient"]}/swagger/oauth2-redirect.html" }, + PostLogoutRedirectUris = { $"{configuration["WebhooksApiClient"]}/swagger/" }, AllowedScopes = { diff --git a/src/Services/Identity/Identity.API/Controllers/AccountController.cs b/src/Services/Identity/Identity.API/Controllers/AccountController.cs deleted file mode 100644 index 22e347fb9..000000000 --- a/src/Services/Identity/Identity.API/Controllers/AccountController.cs +++ /dev/null @@ -1,299 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers -{ - /// - /// This sample controller implements a typical login/logout/provision workflow for local accounts. - /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! - /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval - /// - public class AccountController : Controller - { - //private readonly InMemoryUserLoginService _loginService; - private readonly ILoginService _loginService; - private readonly IIdentityServerInteractionService _interaction; - private readonly IClientStore _clientStore; - private readonly ILogger _logger; - private readonly UserManager _userManager; - private readonly IConfiguration _configuration; - - public AccountController( - - //InMemoryUserLoginService loginService, - ILoginService loginService, - IIdentityServerInteractionService interaction, - IClientStore clientStore, - ILogger logger, - UserManager userManager, - IConfiguration configuration) - { - _loginService = loginService; - _interaction = interaction; - _clientStore = clientStore; - _logger = logger; - _userManager = userManager; - _configuration = configuration; - } - - /// - /// Show login page - /// - [HttpGet] - public async Task Login(string returnUrl) - { - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); - if (context?.IdP != null) - { - throw new NotImplementedException("External login is not implemented!"); - } - - var vm = await BuildLoginViewModelAsync(returnUrl, context); - - ViewData["ReturnUrl"] = returnUrl; - - return View(vm); - } - - /// - /// Handle postback from username/password login - /// - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Login(LoginViewModel model) - { - if (ModelState.IsValid) - { - var user = await _loginService.FindByUsername(model.Email); - - if (await _loginService.ValidateCredentials(user, model.Password)) - { - var tokenLifetime = _configuration.GetValue("TokenLifetimeMinutes", 120); - - var props = new AuthenticationProperties - { - ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(tokenLifetime), - AllowRefresh = true, - RedirectUri = model.ReturnUrl - }; - - if (model.RememberMe) - { - var permanentTokenLifetime = _configuration.GetValue("PermanentTokenLifetimeDays", 365); - - props.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(permanentTokenLifetime); - props.IsPersistent = true; - }; - - await _loginService.SignInAsync(user, props); - - // make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint - if (_interaction.IsValidReturnUrl(model.ReturnUrl)) - { - return Redirect(model.ReturnUrl); - } - - return Redirect("~/"); - } - - ModelState.AddModelError("", "Invalid username or password."); - } - - // something went wrong, show form with error - var vm = await BuildLoginViewModelAsync(model); - - ViewData["ReturnUrl"] = model.ReturnUrl; - - return View(vm); - } - - private async Task BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) - { - var allowLocal = true; - if (context?.ClientId != null) - { - var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); - if (client != null) - { - allowLocal = client.EnableLocalLogin; - } - } - - return new LoginViewModel - { - ReturnUrl = returnUrl, - Email = context?.LoginHint, - }; - } - - private async Task BuildLoginViewModelAsync(LoginViewModel model) - { - var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); - var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context); - vm.Email = model.Email; - vm.RememberMe = model.RememberMe; - return vm; - } - - /// - /// Show logout page - /// - [HttpGet] - public async Task Logout(string logoutId) - { - if (User.Identity.IsAuthenticated == false) - { - // if the user is not authenticated, then just show logged out page - return await Logout(new LogoutViewModel { LogoutId = logoutId }); - } - - //Test for Xamarin. - var context = await _interaction.GetLogoutContextAsync(logoutId); - if (context?.ShowSignoutPrompt == false) - { - //it's safe to automatically sign-out - return await Logout(new LogoutViewModel { LogoutId = logoutId }); - } - - // show the logout prompt. this prevents attacks where the user - // is automatically signed out by another malicious web page. - var vm = new LogoutViewModel - { - LogoutId = logoutId - }; - return View(vm); - } - - /// - /// Handle logout page postback - /// - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Logout(LogoutViewModel model) - { - var idp = User?.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; - - if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider) - { - if (model.LogoutId == null) - { - // if there's no current logout context, we need to create one - // this captures necessary info from the current logged in user - // before we signout and redirect away to the external IdP for signout - model.LogoutId = await _interaction.CreateLogoutContextAsync(); - } - - string url = "/Account/Logout?logoutId=" + model.LogoutId; - - try - { - - // hack: try/catch to handle social providers that throw - await HttpContext.SignOutAsync(idp, new AuthenticationProperties - { - RedirectUri = url - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "LOGOUT ERROR: {ExceptionMessage}", ex.Message); - } - } - - // delete authentication cookie - await HttpContext.SignOutAsync(); - - await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); - - // set this so UI rendering sees an anonymous user - HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - - // get context information (client name, post logout redirect URI and iframe for federated signout) - var logout = await _interaction.GetLogoutContextAsync(model.LogoutId); - - return Redirect(logout?.PostLogoutRedirectUri); - } - - public async Task DeviceLogOut(string redirectUrl) - { - // delete authentication cookie - await HttpContext.SignOutAsync(); - - // set this so UI rendering sees an anonymous user - HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - - return Redirect(redirectUrl); - } - - // GET: /Account/Register - [HttpGet] - [AllowAnonymous] - public IActionResult Register(string returnUrl = null) - { - ViewData["ReturnUrl"] = returnUrl; - return View(); - } - - // - // POST: /Account/Register - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Register(RegisterViewModel model, string returnUrl = null) - { - ViewData["ReturnUrl"] = returnUrl; - if (ModelState.IsValid) - { - var user = new ApplicationUser - { - UserName = model.Email, - Email = model.Email, - CardHolderName = model.User.CardHolderName, - CardNumber = model.User.CardNumber, - CardType = model.User.CardType, - City = model.User.City, - Country = model.User.Country, - Expiration = model.User.Expiration, - LastName = model.User.LastName, - Name = model.User.Name, - Street = model.User.Street, - State = model.User.State, - ZipCode = model.User.ZipCode, - PhoneNumber = model.User.PhoneNumber, - SecurityNumber = model.User.SecurityNumber - }; - var result = await _userManager.CreateAsync(user, model.Password); - if (result.Errors.Count() > 0) - { - AddErrors(result); - // If we got this far, something failed, redisplay form - return View(model); - } - } - - if (returnUrl != null) - { - if (HttpContext.User.Identity.IsAuthenticated) - return Redirect(returnUrl); - else - if (ModelState.IsValid) - return RedirectToAction("login", "account", new { returnUrl = returnUrl }); - else - return View(model); - } - - return RedirectToAction("index", "home"); - } - - [HttpGet] - public IActionResult Redirecting() - { - return View(); - } - - private void AddErrors(IdentityResult result) - { - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Controllers/ConsentController.cs b/src/Services/Identity/Identity.API/Controllers/ConsentController.cs deleted file mode 100644 index 76c27cd23..000000000 --- a/src/Services/Identity/Identity.API/Controllers/ConsentController.cs +++ /dev/null @@ -1,131 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers -{ - /// - /// This controller implements the consent logic - /// - public class ConsentController : Controller - { - private readonly ILogger _logger; - private readonly IClientStore _clientStore; - private readonly IResourceStore _resourceStore; - private readonly IIdentityServerInteractionService _interaction; - - - public ConsentController( - ILogger logger, - IIdentityServerInteractionService interaction, - IClientStore clientStore, - IResourceStore resourceStore) - { - _logger = logger; - _interaction = interaction; - _clientStore = clientStore; - _resourceStore = resourceStore; - } - - /// - /// Shows the consent screen - /// - /// - /// - [HttpGet] - public async Task Index(string returnUrl) - { - var vm = await BuildViewModelAsync(returnUrl); - ViewData["ReturnUrl"] = returnUrl; - if (vm != null) - { - return View("Index", vm); - } - - return View("Error"); - } - - /// - /// Handles the consent screen postback - /// - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Index(ConsentInputModel model) - { - // parse the return URL back to an AuthorizeRequest object - var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); - ConsentResponse response = null; - - // user clicked 'no' - send back the standard 'access_denied' response - if (model.Button == "no") - { - response = ConsentResponse.Denied; - } - // user clicked 'yes' - validate the data - else if (model.Button == "yes") - { - // if the user consented to some scope, build the response model - if (model.ScopesConsented != null && model.ScopesConsented.Any()) - { - response = new ConsentResponse - { - RememberConsent = model.RememberConsent, - ScopesConsented = model.ScopesConsented - }; - } - else - { - ModelState.AddModelError("", "You must pick at least one permission."); - } - } - else - { - ModelState.AddModelError("", "Invalid Selection"); - } - - if (response != null) - { - // communicate outcome of consent back to identityserver - await _interaction.GrantConsentAsync(request, response); - - // redirect back to authorization endpoint - return Redirect(model.ReturnUrl); - } - - var vm = await BuildViewModelAsync(model.ReturnUrl, model); - if (vm != null) - { - return View("Index", vm); - } - - return View("Error"); - } - - async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) - { - var request = await _interaction.GetAuthorizationContextAsync(returnUrl); - if (request != null) - { - var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); - if (client != null) - { - var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); - if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any())) - { - return new ConsentViewModel(model, returnUrl, request, client, resources); - } - else - { - _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); - } - } - else - { - _logger.LogError("Invalid client id: {0}", request.ClientId); - } - } - else - { - _logger.LogError("No consent request matching request: {0}", returnUrl); - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Controllers/HomeController.cs b/src/Services/Identity/Identity.API/Controllers/HomeController.cs deleted file mode 100644 index 3c31c4ab1..000000000 --- a/src/Services/Identity/Identity.API/Controllers/HomeController.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers -{ - public class HomeController : Controller - { - private readonly IIdentityServerInteractionService _interaction; - private readonly IOptionsSnapshot _settings; - private readonly IRedirectService _redirectSvc; - - public HomeController(IIdentityServerInteractionService interaction, IOptionsSnapshot settings, IRedirectService redirectSvc) - { - _interaction = interaction; - _settings = settings; - _redirectSvc = redirectSvc; - } - - public IActionResult Index(string returnUrl) - { - return View(); - } - - public IActionResult ReturnToOriginalApplication(string returnUrl) - { - if (returnUrl != null) - return Redirect(_redirectSvc.ExtractRedirectUriFromReturnUrl(returnUrl)); - else - return RedirectToAction("Index", "Home"); - } - - /// - /// Shows the error page - /// - public async Task Error(string errorId) - { - var vm = new ErrorViewModel(); - - // retrieve error details from identityserver - var message = await _interaction.GetErrorContextAsync(errorId); - if (message != null) - { - vm.Error = message; - } - - return View("Error", vm); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Data/ApplicationDbContext.cs b/src/Services/Identity/Identity.API/Data/ApplicationDbContext.cs index 227406008..2d04400d7 100644 --- a/src/Services/Identity/Identity.API/Data/ApplicationDbContext.cs +++ b/src/Services/Identity/Identity.API/Data/ApplicationDbContext.cs @@ -1,18 +1,17 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Data +namespace Microsoft.eShopOnContainers.Services.Identity.API.Data; + +public class ApplicationDbContext : IdentityDbContext { - public class ApplicationDbContext : IdentityDbContext + public ApplicationDbContext(DbContextOptions options) + : base(options) { - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } + } - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); } } diff --git a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs b/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs deleted file mode 100644 index 6ee9fd499..000000000 --- a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs +++ /dev/null @@ -1,218 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Data -{ - using Microsoft.Extensions.Logging; - public class ApplicationDbContextSeed - { - private readonly IPasswordHasher _passwordHasher = new PasswordHasher(); - - public async Task SeedAsync(ApplicationDbContext context, IWebHostEnvironment env, - ILogger logger, IOptions settings, int? retry = 0) - { - int retryForAvaiability = retry.Value; - - try - { - var useCustomizationData = settings.Value.UseCustomizationData; - var contentRootPath = env.ContentRootPath; - var webroot = env.WebRootPath; - - if (!context.Users.Any()) - { - context.Users.AddRange(useCustomizationData - ? GetUsersFromFile(contentRootPath, logger) - : GetDefaultUser()); - - await context.SaveChangesAsync(); - } - - if (useCustomizationData) - { - GetPreconfiguredImages(contentRootPath, webroot, logger); - } - } - catch (Exception ex) - { - if (retryForAvaiability < 10) - { - retryForAvaiability++; - - logger.LogError(ex, "EXCEPTION ERROR while migrating {DbContextName}", nameof(ApplicationDbContext)); - - await SeedAsync(context, env, logger, settings, retryForAvaiability); - } - } - } - - private IEnumerable GetUsersFromFile(string contentRootPath, ILogger logger) - { - string csvFileUsers = Path.Combine(contentRootPath, "Setup", "Users.csv"); - - if (!File.Exists(csvFileUsers)) - { - return GetDefaultUser(); - } - - string[] csvheaders; - try - { - string[] requiredHeaders = { - "cardholdername", "cardnumber", "cardtype", "city", "country", - "email", "expiration", "lastname", "name", "phonenumber", - "username", "zipcode", "state", "street", "securitynumber", - "normalizedemail", "normalizedusername", "password" - }; - csvheaders = GetHeaders(requiredHeaders, csvFileUsers); - } - catch (Exception ex) - { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); - - return GetDefaultUser(); - } - - List users = File.ReadAllLines(csvFileUsers) - .Skip(1) // skip header column - .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)")) - .SelectTry(column => CreateApplicationUser(column, csvheaders)) - .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) - .Where(x => x != null) - .ToList(); - - return users; - } - - private ApplicationUser CreateApplicationUser(string[] column, string[] headers) - { - if (column.Count() != headers.Count()) - { - throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'"); - } - - string cardtypeString = column[Array.IndexOf(headers, "cardtype")].Trim('"').Trim(); - if (!int.TryParse(cardtypeString, out int cardtype)) - { - throw new Exception($"cardtype='{cardtypeString}' is not a number"); - } - - var user = new ApplicationUser - { - CardHolderName = column[Array.IndexOf(headers, "cardholdername")].Trim('"').Trim(), - CardNumber = column[Array.IndexOf(headers, "cardnumber")].Trim('"').Trim(), - CardType = cardtype, - City = column[Array.IndexOf(headers, "city")].Trim('"').Trim(), - Country = column[Array.IndexOf(headers, "country")].Trim('"').Trim(), - Email = column[Array.IndexOf(headers, "email")].Trim('"').Trim(), - Expiration = column[Array.IndexOf(headers, "expiration")].Trim('"').Trim(), - Id = Guid.NewGuid().ToString(), - LastName = column[Array.IndexOf(headers, "lastname")].Trim('"').Trim(), - Name = column[Array.IndexOf(headers, "name")].Trim('"').Trim(), - PhoneNumber = column[Array.IndexOf(headers, "phonenumber")].Trim('"').Trim(), - UserName = column[Array.IndexOf(headers, "username")].Trim('"').Trim(), - ZipCode = column[Array.IndexOf(headers, "zipcode")].Trim('"').Trim(), - State = column[Array.IndexOf(headers, "state")].Trim('"').Trim(), - Street = column[Array.IndexOf(headers, "street")].Trim('"').Trim(), - SecurityNumber = column[Array.IndexOf(headers, "securitynumber")].Trim('"').Trim(), - NormalizedEmail = column[Array.IndexOf(headers, "normalizedemail")].Trim('"').Trim(), - NormalizedUserName = column[Array.IndexOf(headers, "normalizedusername")].Trim('"').Trim(), - SecurityStamp = Guid.NewGuid().ToString("D"), - PasswordHash = column[Array.IndexOf(headers, "password")].Trim('"').Trim(), // Note: This is the password - }; - - user.PasswordHash = _passwordHasher.HashPassword(user, user.PasswordHash); - - return user; - } - - private IEnumerable GetDefaultUser() - { - var user = - new ApplicationUser() - { - CardHolderName = "DemoUser", - CardNumber = "4012888888881881", - CardType = 1, - City = "Redmond", - Country = "U.S.", - Email = "demouser@microsoft.com", - Expiration = "12/25", - Id = Guid.NewGuid().ToString(), - LastName = "DemoLastName", - Name = "DemoUser", - PhoneNumber = "1234567890", - UserName = "demouser@microsoft.com", - ZipCode = "98052", - State = "WA", - Street = "15703 NE 61st Ct", - SecurityNumber = "535", - NormalizedEmail = "DEMOUSER@MICROSOFT.COM", - NormalizedUserName = "DEMOUSER@MICROSOFT.COM", - SecurityStamp = Guid.NewGuid().ToString("D"), - }; - - user.PasswordHash = _passwordHasher.HashPassword(user, "Pass@word1"); - - return new List() - { - user - }; - } - - static string[] GetHeaders(string[] requiredHeaders, string csvfile) - { - string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(','); - - if (csvheaders.Count() != requiredHeaders.Count()) - { - throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'"); - } - - foreach (var requiredHeader in requiredHeaders) - { - if (!csvheaders.Contains(requiredHeader)) - { - throw new Exception($"does not contain required header '{requiredHeader}'"); - } - } - - return csvheaders; - } - - static void GetPreconfiguredImages(string contentRootPath, string webroot, ILogger logger) - { - try - { - string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip"); - if (!File.Exists(imagesZipFile)) - { - logger.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile); - return; - } - - string imagePath = Path.Combine(webroot, "images"); - string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray(); - - using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read); - foreach (ZipArchiveEntry entry in zip.Entries) - { - if (imageFiles.Contains(entry.Name)) - { - string destinationFilename = Path.Combine(imagePath, entry.Name); - if (File.Exists(destinationFilename)) - { - File.Delete(destinationFilename); - } - entry.ExtractToFile(destinationFilename); - } - else - { - logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile); - } - } - } - catch (Exception ex) - { - logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); ; - } - } - } -} diff --git a/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs b/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs deleted file mode 100644 index e3df90fdd..000000000 --- a/src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs +++ /dev/null @@ -1,73 +0,0 @@ -using IdentityServer4.EntityFramework.Entities; - -namespace Microsoft.eShopOnContainers.Services.Identity.API.Data -{ - public class ConfigurationDbContextSeed - { - public async Task SeedAsync(ConfigurationDbContext context, IConfiguration configuration) - { - - //callbacks urls from config: - var clientUrls = new Dictionary(); - - clientUrls.Add("Mvc", configuration.GetValue("MvcClient")); - clientUrls.Add("Spa", configuration.GetValue("SpaClient")); - clientUrls.Add("Xamarin", configuration.GetValue("XamarinCallback")); - clientUrls.Add("BasketApi", configuration.GetValue("BasketApiClient")); - clientUrls.Add("OrderingApi", configuration.GetValue("OrderingApiClient")); - clientUrls.Add("MobileShoppingAgg", configuration.GetValue("MobileShoppingAggClient")); - clientUrls.Add("WebShoppingAgg", configuration.GetValue("WebShoppingAggClient")); - clientUrls.Add("WebhooksApi", configuration.GetValue("WebhooksApiClient")); - clientUrls.Add("WebhooksWeb", configuration.GetValue("WebhooksWebClient")); - - if (!context.Clients.Any()) - { - foreach (var client in Config.GetClients(clientUrls)) - { - context.Clients.Add(client.ToEntity()); - } - await context.SaveChangesAsync(); - } - // Checking always for old redirects to fix existing deployments - // to use new swagger-ui redirect uri as of v3.0.0 - // There should be no problem for new ones - // ref: https://github.com/dotnet-architecture/eShopOnContainers/issues/586 - else - { - List oldRedirects = (await context.Clients.Include(c => c.RedirectUris).ToListAsync()) - .SelectMany(c => c.RedirectUris) - .Where(ru => ru.RedirectUri.EndsWith("/o2c.html")) - .ToList(); - - if (oldRedirects.Any()) - { - foreach (var ru in oldRedirects) - { - ru.RedirectUri = ru.RedirectUri.Replace("/o2c.html", "/oauth2-redirect.html"); - context.Update(ru.Client); - } - await context.SaveChangesAsync(); - } - } - - if (!context.IdentityResources.Any()) - { - foreach (var resource in Config.GetResources()) - { - context.IdentityResources.Add(resource.ToEntity()); - } - await context.SaveChangesAsync(); - } - - if (!context.ApiResources.Any()) - { - foreach (var api in Config.GetApis()) - { - context.ApiResources.Add(api.ToEntity()); - } - - await context.SaveChangesAsync(); - } - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.Designer.cs b/src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.Designer.cs similarity index 95% rename from src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.Designer.cs rename to src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.Designer.cs index b6c43abf5..d8c737ae4 100644 --- a/src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.Designer.cs +++ b/src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.Designer.cs @@ -7,10 +7,10 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.eShopOnContainers.Services.Identity.API.Data; -namespace Identity.API.Migrations +namespace Microsoft.eShopOnContainers.Services.Identity.API.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20210813072445_InitialMigration")] + [Migration("20210914100206_InitialMigration")] partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,7 +18,7 @@ namespace Identity.API.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("ProductVersion", "5.0.9") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -45,7 +45,7 @@ namespace Identity.API.Migrations .HasDatabaseName("RoleNameIndex") .HasFilter("[NormalizedName] IS NOT NULL"); - b.ToTable("AspNetRoles", (string)null); + b.ToTable("AspNetRoles"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -69,7 +69,7 @@ namespace Identity.API.Migrations b.HasIndex("RoleId"); - b.ToTable("AspNetRoleClaims", (string)null); + b.ToTable("AspNetRoleClaims"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -93,7 +93,7 @@ namespace Identity.API.Migrations b.HasIndex("UserId"); - b.ToTable("AspNetUserClaims", (string)null); + b.ToTable("AspNetUserClaims"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -115,7 +115,7 @@ namespace Identity.API.Migrations b.HasIndex("UserId"); - b.ToTable("AspNetUserLogins", (string)null); + b.ToTable("AspNetUserLogins"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -130,7 +130,7 @@ namespace Identity.API.Migrations b.HasIndex("RoleId"); - b.ToTable("AspNetUserRoles", (string)null); + b.ToTable("AspNetUserRoles"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -149,7 +149,7 @@ namespace Identity.API.Migrations b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AspNetUserTokens", (string)null); + b.ToTable("AspNetUserTokens"); }); modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Identity.API.Models.ApplicationUser", b => @@ -261,7 +261,7 @@ namespace Identity.API.Migrations .HasDatabaseName("UserNameIndex") .HasFilter("[NormalizedUserName] IS NOT NULL"); - b.ToTable("AspNetUsers", (string)null); + b.ToTable("AspNetUsers"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => diff --git a/src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.cs b/src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.cs similarity index 99% rename from src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.cs rename to src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.cs index 88587a677..c6a89ab73 100644 --- a/src/Services/Identity/Identity.API/Migrations/20210813072445_InitialMigration.cs +++ b/src/Services/Identity/Identity.API/Data/Migrations/20210914100206_InitialMigration.cs @@ -1,7 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Identity.API.Migrations +namespace Microsoft.eShopOnContainers.Services.Identity.API.Data.Migrations { public partial class InitialMigration : Migration { diff --git a/src/Services/Identity/Identity.API/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Services/Identity/Identity.API/Data/Migrations/ApplicationDbContextModelSnapshot.cs similarity index 95% rename from src/Services/Identity/Identity.API/Migrations/ApplicationDbContextModelSnapshot.cs rename to src/Services/Identity/Identity.API/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 0076eb5bf..8216173a7 100644 --- a/src/Services/Identity/Identity.API/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Services/Identity/Identity.API/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.eShopOnContainers.Services.Identity.API.Data; -namespace Identity.API.Migrations +namespace Microsoft.eShopOnContainers.Services.Identity.API.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] partial class ApplicationDbContextModelSnapshot : ModelSnapshot @@ -16,7 +16,7 @@ namespace Identity.API.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("ProductVersion", "5.0.9") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -43,7 +43,7 @@ namespace Identity.API.Migrations .HasDatabaseName("RoleNameIndex") .HasFilter("[NormalizedName] IS NOT NULL"); - b.ToTable("AspNetRoles", (string)null); + b.ToTable("AspNetRoles"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -67,7 +67,7 @@ namespace Identity.API.Migrations b.HasIndex("RoleId"); - b.ToTable("AspNetRoleClaims", (string)null); + b.ToTable("AspNetRoleClaims"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -91,7 +91,7 @@ namespace Identity.API.Migrations b.HasIndex("UserId"); - b.ToTable("AspNetUserClaims", (string)null); + b.ToTable("AspNetUserClaims"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -113,7 +113,7 @@ namespace Identity.API.Migrations b.HasIndex("UserId"); - b.ToTable("AspNetUserLogins", (string)null); + b.ToTable("AspNetUserLogins"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -128,7 +128,7 @@ namespace Identity.API.Migrations b.HasIndex("RoleId"); - b.ToTable("AspNetUserRoles", (string)null); + b.ToTable("AspNetUserRoles"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -147,7 +147,7 @@ namespace Identity.API.Migrations b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AspNetUserTokens", (string)null); + b.ToTable("AspNetUserTokens"); }); modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Identity.API.Models.ApplicationUser", b => @@ -259,7 +259,7 @@ namespace Identity.API.Migrations .HasDatabaseName("UserNameIndex") .HasFilter("[NormalizedUserName] IS NOT NULL"); - b.ToTable("AspNetUsers", (string)null); + b.ToTable("AspNetUsers"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => diff --git a/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs b/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs deleted file mode 100644 index 8912882f4..000000000 --- a/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Devspaces -{ - using Microsoft.Extensions.Logging; - - public class DevspacesRedirectUriValidator : IRedirectUriValidator - { - private readonly ILogger _logger; - public DevspacesRedirectUriValidator(ILogger logger) - { - _logger = logger; - } - - public Task IsPostLogoutRedirectUriValidAsync(string requestedUri, IdentityServer4.Models.Client client) - { - - _logger.LogInformation("Client {ClientName} used post logout uri {RequestedUri}.", client.ClientName, requestedUri); - return Task.FromResult(true); - } - - public Task IsRedirectUriValidAsync(string requestedUri, IdentityServer4.Models.Client client) - { - _logger.LogInformation("Client {ClientName} used post logout uri {RequestedUri}.", client.ClientName, requestedUri); - return Task.FromResult(true); - } - - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Devspaces/IdentityDevspacesBuilderExtensions.cs b/src/Services/Identity/Identity.API/Devspaces/IdentityDevspacesBuilderExtensions.cs deleted file mode 100644 index 201c85ab2..000000000 --- a/src/Services/Identity/Identity.API/Devspaces/IdentityDevspacesBuilderExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Devspaces -{ - static class IdentityDevspacesBuilderExtensions - { - public static IIdentityServerBuilder AddDevspacesIfNeeded(this IIdentityServerBuilder builder, bool useDevspaces) - { - if (useDevspaces) - { - builder.AddRedirectUriValidator(); - } - return builder; - } - } -} diff --git a/src/Services/Identity/Identity.API/Dockerfile b/src/Services/Identity/Identity.API/Dockerfile index 674cc4ec1..574eb277a 100644 --- a/src/Services/Identity/Identity.API/Dockerfile +++ b/src/Services/Identity/Identity.API/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles @@ -11,7 +11,6 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" -COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj" COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" @@ -33,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" +COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" @@ -42,6 +42,7 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj" COPY "docker-compose.dcproj" "docker-compose.dcproj" +COPY "Directory.Packages.props" "Directory.Packages.props" COPY "NuGet.config" "NuGet.config" RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln" diff --git a/src/Services/Identity/Identity.API/Dockerfile.develop b/src/Services/Identity/Identity.API/Dockerfile.develop index 9afc17772..cb38e5b4d 100644 --- a/src/Services/Identity/Identity.API/Dockerfile.develop +++ b/src/Services/Identity/Identity.API/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG BUILD_CONFIGURATION=Debug ENV ASPNETCORE_ENVIRONMENT=Development ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/src/Services/Identity/Identity.API/Extensions/LinqSelectExtensions.cs b/src/Services/Identity/Identity.API/Extensions/LinqSelectExtensions.cs deleted file mode 100644 index 0e2c4c72b..000000000 --- a/src/Services/Identity/Identity.API/Extensions/LinqSelectExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Extensions -{ - public static class LinqSelectExtensions - { - public static IEnumerable> SelectTry(this IEnumerable enumerable, Func selector) - { - foreach (TSource element in enumerable) - { - SelectTryResult returnedValue; - try - { - returnedValue = new SelectTryResult(element, selector(element), null); - } - catch (Exception ex) - { - returnedValue = new SelectTryResult(element, default(TResult), ex); - } - yield return returnedValue; - } - } - - public static IEnumerable OnCaughtException(this IEnumerable> enumerable, Func exceptionHandler) - { - return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException)); - } - - public static IEnumerable OnCaughtException(this IEnumerable> enumerable, Func exceptionHandler) - { - return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException)); - } - - public class SelectTryResult - { - internal SelectTryResult(TSource source, TResult result, Exception exception) - { - Source = source; - Result = result; - CaughtException = exception; - } - - public TSource Source { get; private set; } - public TResult Result { get; private set; } - public Exception CaughtException { get; private set; } - } - } -} diff --git a/src/Services/Identity/Identity.API/Factories/ApplicationDbContextFactory.cs b/src/Services/Identity/Identity.API/Factories/ApplicationDbContextFactory.cs deleted file mode 100644 index c1fb0198c..000000000 --- a/src/Services/Identity/Identity.API/Factories/ApplicationDbContextFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Identity.API.Factories -{ - public class ApplicationDbContextFactory : IDesignTimeDbContextFactory - { - public ApplicationDbContext CreateDbContext(string[] args) - { - var config = new ConfigurationBuilder() - .SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var optionsBuilder = new DbContextOptionsBuilder(); - - optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); - - return new ApplicationDbContext(optionsBuilder.Options); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Factories/ConfigurationDbContextFactory.cs b/src/Services/Identity/Identity.API/Factories/ConfigurationDbContextFactory.cs deleted file mode 100644 index 0fdbe8598..000000000 --- a/src/Services/Identity/Identity.API/Factories/ConfigurationDbContextFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Identity.API.Factories -{ - public class ConfigurationDbContextFactory : IDesignTimeDbContextFactory - { - public ConfigurationDbContext CreateDbContext(string[] args) - { - var config = new ConfigurationBuilder() - .SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var optionsBuilder = new DbContextOptionsBuilder(); - var storeOptions = new ConfigurationStoreOptions(); - - optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); - - return new ConfigurationDbContext(optionsBuilder.Options, storeOptions); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Factories/PersistedGrantDbContextFactory.cs b/src/Services/Identity/Identity.API/Factories/PersistedGrantDbContextFactory.cs deleted file mode 100644 index 83380dfd0..000000000 --- a/src/Services/Identity/Identity.API/Factories/PersistedGrantDbContextFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Identity.API.Factories -{ - public class PersistedGrantDbContextFactory : IDesignTimeDbContextFactory - { - public PersistedGrantDbContext CreateDbContext(string[] args) - { - var config = new ConfigurationBuilder() - .SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var optionsBuilder = new DbContextOptionsBuilder(); - var operationOptions = new OperationalStoreOptions(); - - optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); - - return new PersistedGrantDbContext(optionsBuilder.Options, operationOptions); - } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/GlobalUsings.cs b/src/Services/Identity/Identity.API/GlobalUsings.cs index bfbb354db..439583c53 100644 --- a/src/Services/Identity/Identity.API/GlobalUsings.cs +++ b/src/Services/Identity/Identity.API/GlobalUsings.cs @@ -1,73 +1,46 @@ -global using Microsoft.eShopOnContainers.Services.Identity.API.Extensions; -global using System.IO.Compression; -global using Autofac.Extensions.DependencyInjection; -global using Autofac; -global using Azure.Core; -global using Azure.Identity; -global using HealthChecks.UI.Client; +global using System; +global using System.Collections.Generic; +global using System.ComponentModel.DataAnnotations; +global using System.IdentityModel.Tokens.Jwt; +global using System.Linq; +global using System.Security.Claims; +global using System.Text.RegularExpressions; +global using System.Threading.Tasks; +global using Duende.IdentityServer; +global using Duende.IdentityServer.Configuration; +global using Duende.IdentityServer.Events; +global using Duende.IdentityServer.Extensions; +global using Duende.IdentityServer.Models; +global using Duende.IdentityServer.Services; +global using Duende.IdentityServer.Stores; +global using Duende.IdentityServer.Validation; global using IdentityModel; -global using IdentityServer4.EntityFramework.DbContexts; -global using IdentityServer4.EntityFramework.Mappers; -global using IdentityServer4.EntityFramework.Options; -global using IdentityServer4.Models; -global using IdentityServer4.Services; -global using IdentityServer4.Stores; -global using IdentityServer4.Validation; -global using IdentityServer4; global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Diagnostics.HealthChecks; global using Microsoft.AspNetCore.Hosting; -global using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Identity; -global using Microsoft.AspNetCore.Mvc.Rendering; +global using Microsoft.AspNetCore.Identity.EntityFrameworkCore; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore; -global using Microsoft.EntityFrameworkCore.Design; +global using Microsoft.AspNetCore.Mvc.Filters; +global using Microsoft.AspNetCore.Mvc.Rendering; +global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Infrastructure; global using Microsoft.EntityFrameworkCore.Metadata; global using Microsoft.EntityFrameworkCore.Migrations; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.eShopOnContainers.Services.Identity.API.Certificates; +global using Microsoft.eShopOnContainers.Services.Identity.API; global using Microsoft.eShopOnContainers.Services.Identity.API.Configuration; global using Microsoft.eShopOnContainers.Services.Identity.API.Data; -global using Microsoft.eShopOnContainers.Services.Identity.API.Devspaces; -global using Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels; global using Microsoft.eShopOnContainers.Services.Identity.API.Models; global using Microsoft.eShopOnContainers.Services.Identity.API.Services; -global using Microsoft.eShopOnContainers.Services.Identity.API; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Diagnostics.HealthChecks; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Polly; -global using Serilog; -global using StackExchange.Redis; -global using System.Collections.Generic; -global using System.ComponentModel.DataAnnotations; -global using System.Data.SqlClient; -global using System.IdentityModel.Tokens.Jwt; -global using System.IO; -global using System.Linq; -global using System.Reflection; -global using System.Security.Claims; -global using System.Security.Cryptography.X509Certificates; -global using System.Text.RegularExpressions; -global using System.Threading.Tasks; -global using System; - - - - - - - - - - +global using Services.Common; diff --git a/src/Services/Identity/Identity.API/IWebHostExtensions.cs b/src/Services/Identity/Identity.API/IWebHostExtensions.cs deleted file mode 100644 index 57c386559..000000000 --- a/src/Services/Identity/Identity.API/IWebHostExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace Microsoft.AspNetCore.Hosting -{ - public static class IWebHostExtensions - { - public static bool IsInKubernetes(this IWebHost webHost) - { - var cfg = webHost.Services.GetService(); - var orchestratorType = cfg.GetValue("OrchestratorType"); - return orchestratorType?.ToUpper() == "K8S"; - } - - public static IWebHost MigrateDbContext(this IWebHost webHost, Action seeder) where TContext : DbContext - { - var underK8s = webHost.IsInKubernetes(); - - using var scope = webHost.Services.CreateScope(); - var services = scope.ServiceProvider; - var logger = services.GetRequiredService>(); - var context = services.GetService(); - - try - { - logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name); - - if (underK8s) - { - InvokeSeeder(seeder, context, services); - } - else - { - var retries = 10; - var retry = Policy.Handle() - .WaitAndRetry( - retryCount: retries, - sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - onRetry: (exception, timeSpan, retry, ctx) => - { - logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries); - }); - - //if the sql server container is not created on run docker compose this - //migration can't fail for network related exception. The retry options for DbContext only - //apply to transient exceptions - // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) - retry.Execute(() => InvokeSeeder(seeder, context, services)); - } - - logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name); - if (underK8s) - { - throw; // Rethrow under k8s because we rely on k8s to re-run the pod - } - } - - return webHost; - } - - private static void InvokeSeeder(Action seeder, TContext context, IServiceProvider services) - where TContext : DbContext - { - context.Database.Migrate(); - seeder(context, services); - } - } -} diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index 67d46a95f..91d74a376 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -1,72 +1,65 @@  - net6.0 + net7.0 aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5 ..\..\..\..\docker-compose.dcproj - false - true - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - + + + + - + + + + + + + + + + + + + + + + + + - - PreserveNewest - + - + + PreserveNewest + true + PreserveNewest + - + + + + + diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.Designer.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.Designer.cs deleted file mode 100644 index c5e7b3457..000000000 --- a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.Designer.cs +++ /dev/null @@ -1,911 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Identity.API.Migrations.ConfigurationDb -{ - [DbContext(typeof(ConfigurationDbContext))] - [Migration("20210813072543_InitialMigration")] - partial class InitialMigration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Enabled") - .HasColumnType("bit"); - - b.Property("LastAccessed") - .HasColumnType("datetime2"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiResources", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Emphasize") - .HasColumnType("bit"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiScopes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiScopeId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiScopeId"); - - b.ToTable("ApiScopeClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("nvarchar(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiSecrets", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("int"); - - b.Property("AccessTokenLifetime") - .HasColumnType("int"); - - b.Property("AccessTokenType") - .HasColumnType("int"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("bit"); - - b.Property("AllowOfflineAccess") - .HasColumnType("bit"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("bit"); - - b.Property("AllowRememberConsent") - .HasColumnType("bit"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("bit"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("bit"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("int"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("bit"); - - b.Property("BackChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ClientClaimsPrefix") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ConsentLifetime") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("int"); - - b.Property("EnableLocalLogin") - .HasColumnType("bit"); - - b.Property("Enabled") - .HasColumnType("bit"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("bit"); - - b.Property("FrontChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("int"); - - b.Property("IncludeJwtId") - .HasColumnType("bit"); - - b.Property("LastAccessed") - .HasColumnType("datetime2"); - - b.Property("LogoUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("PairWiseSubjectSalt") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ProtocolType") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("int"); - - b.Property("RefreshTokenUsage") - .HasColumnType("int"); - - b.Property("RequireClientSecret") - .HasColumnType("bit"); - - b.Property("RequireConsent") - .HasColumnType("bit"); - - b.Property("RequirePkce") - .HasColumnType("bit"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("int"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.Property("UserCodeType") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("UserSsoLifetime") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("Clients", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Origin") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("nvarchar(150)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientCorsOrigins", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("GrantType") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientIdPRestrictions", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("PostLogoutRedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientPostLogoutRedirectUris", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("RedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("nvarchar(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientSecrets", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("IdentityResourceId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Emphasize") - .HasColumnType("bit"); - - b.Property("Enabled") - .HasColumnType("bit"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("IdentityResources", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("IdentityResourceId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") - .WithMany("UserClaims") - .HasForeignKey("ApiScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiScope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.cs deleted file mode 100644 index 57f508711..000000000 --- a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20210813072543_InitialMigration.cs +++ /dev/null @@ -1,607 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Identity.API.Migrations.ConfigurationDb -{ - public partial class InitialMigration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "ApiResources", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Enabled = table.Column(type: "bit", nullable: false), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Created = table.Column(type: "datetime2", nullable: false), - Updated = table.Column(type: "datetime2", nullable: true), - LastAccessed = table.Column(type: "datetime2", nullable: true), - NonEditable = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiResources", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Clients", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Enabled = table.Column(type: "bit", nullable: false), - ClientId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - ProtocolType = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - RequireClientSecret = table.Column(type: "bit", nullable: false), - ClientName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - ClientUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - LogoUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - RequireConsent = table.Column(type: "bit", nullable: false), - AllowRememberConsent = table.Column(type: "bit", nullable: false), - AlwaysIncludeUserClaimsInIdToken = table.Column(type: "bit", nullable: false), - RequirePkce = table.Column(type: "bit", nullable: false), - AllowPlainTextPkce = table.Column(type: "bit", nullable: false), - AllowAccessTokensViaBrowser = table.Column(type: "bit", nullable: false), - FrontChannelLogoutUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - FrontChannelLogoutSessionRequired = table.Column(type: "bit", nullable: false), - BackChannelLogoutUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - BackChannelLogoutSessionRequired = table.Column(type: "bit", nullable: false), - AllowOfflineAccess = table.Column(type: "bit", nullable: false), - IdentityTokenLifetime = table.Column(type: "int", nullable: false), - AccessTokenLifetime = table.Column(type: "int", nullable: false), - AuthorizationCodeLifetime = table.Column(type: "int", nullable: false), - ConsentLifetime = table.Column(type: "int", nullable: true), - AbsoluteRefreshTokenLifetime = table.Column(type: "int", nullable: false), - SlidingRefreshTokenLifetime = table.Column(type: "int", nullable: false), - RefreshTokenUsage = table.Column(type: "int", nullable: false), - UpdateAccessTokenClaimsOnRefresh = table.Column(type: "bit", nullable: false), - RefreshTokenExpiration = table.Column(type: "int", nullable: false), - AccessTokenType = table.Column(type: "int", nullable: false), - EnableLocalLogin = table.Column(type: "bit", nullable: false), - IncludeJwtId = table.Column(type: "bit", nullable: false), - AlwaysSendClientClaims = table.Column(type: "bit", nullable: false), - ClientClaimsPrefix = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - PairWiseSubjectSalt = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Created = table.Column(type: "datetime2", nullable: false), - Updated = table.Column(type: "datetime2", nullable: true), - LastAccessed = table.Column(type: "datetime2", nullable: true), - UserSsoLifetime = table.Column(type: "int", nullable: true), - UserCodeType = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - DeviceCodeLifetime = table.Column(type: "int", nullable: false), - NonEditable = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Clients", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "IdentityResources", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Enabled = table.Column(type: "bit", nullable: false), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Required = table.Column(type: "bit", nullable: false), - Emphasize = table.Column(type: "bit", nullable: false), - ShowInDiscoveryDocument = table.Column(type: "bit", nullable: false), - Created = table.Column(type: "datetime2", nullable: false), - Updated = table.Column(type: "datetime2", nullable: true), - NonEditable = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityResources", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ApiClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ApiResourceId = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiClaims", x => x.Id); - table.ForeignKey( - name: "FK_ApiClaims_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiProperties", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ApiResourceId = table.Column(type: "int", nullable: false), - Key = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiProperties", x => x.Id); - table.ForeignKey( - name: "FK_ApiProperties_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiScopes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Required = table.Column(type: "bit", nullable: false), - Emphasize = table.Column(type: "bit", nullable: false), - ShowInDiscoveryDocument = table.Column(type: "bit", nullable: false), - ApiResourceId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiScopes", x => x.Id); - table.ForeignKey( - name: "FK_ApiScopes_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiSecrets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ApiResourceId = table.Column(type: "int", nullable: false), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Value = table.Column(type: "nvarchar(4000)", maxLength: 4000, nullable: false), - Expiration = table.Column(type: "datetime2", nullable: true), - Type = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Created = table.Column(type: "datetime2", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiSecrets", x => x.Id); - table.ForeignKey( - name: "FK_ApiSecrets_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Type = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientClaims", x => x.Id); - table.ForeignKey( - name: "FK_ClientClaims_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientCorsOrigins", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Origin = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id); - table.ForeignKey( - name: "FK_ClientCorsOrigins_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientGrantTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - GrantType = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientGrantTypes", x => x.Id); - table.ForeignKey( - name: "FK_ClientGrantTypes_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientIdPRestrictions", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Provider = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id); - table.ForeignKey( - name: "FK_ClientIdPRestrictions_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientPostLogoutRedirectUris", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - PostLogoutRedirectUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id); - table.ForeignKey( - name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientProperties", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ClientId = table.Column(type: "int", nullable: false), - Key = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientProperties", x => x.Id); - table.ForeignKey( - name: "FK_ClientProperties_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientRedirectUris", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RedirectUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientRedirectUris", x => x.Id); - table.ForeignKey( - name: "FK_ClientRedirectUris_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientScopes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Scope = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - ClientId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientScopes", x => x.Id); - table.ForeignKey( - name: "FK_ClientScopes_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientSecrets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ClientId = table.Column(type: "int", nullable: false), - Description = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - Value = table.Column(type: "nvarchar(4000)", maxLength: 4000, nullable: false), - Expiration = table.Column(type: "datetime2", nullable: true), - Type = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Created = table.Column(type: "datetime2", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientSecrets", x => x.Id); - table.ForeignKey( - name: "FK_ClientSecrets_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IdentityClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - IdentityResourceId = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityClaims", x => x.Id); - table.ForeignKey( - name: "FK_IdentityClaims_IdentityResources_IdentityResourceId", - column: x => x.IdentityResourceId, - principalTable: "IdentityResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IdentityProperties", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - IdentityResourceId = table.Column(type: "int", nullable: false), - Key = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityProperties", x => x.Id); - table.ForeignKey( - name: "FK_IdentityProperties_IdentityResources_IdentityResourceId", - column: x => x.IdentityResourceId, - principalTable: "IdentityResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiScopeClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ApiScopeId = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); - table.ForeignKey( - name: "FK_ApiScopeClaims_ApiScopes_ApiScopeId", - column: x => x.ApiScopeId, - principalTable: "ApiScopes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_ApiClaims_ApiResourceId", - table: "ApiClaims", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiProperties_ApiResourceId", - table: "ApiProperties", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiResources_Name", - table: "ApiResources", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopeClaims_ApiScopeId", - table: "ApiScopeClaims", - column: "ApiScopeId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopes_ApiResourceId", - table: "ApiScopes", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopes_Name", - table: "ApiScopes", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ApiSecrets_ApiResourceId", - table: "ApiSecrets", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientClaims_ClientId", - table: "ClientClaims", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientCorsOrigins_ClientId", - table: "ClientCorsOrigins", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientGrantTypes_ClientId", - table: "ClientGrantTypes", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientIdPRestrictions_ClientId", - table: "ClientIdPRestrictions", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientPostLogoutRedirectUris_ClientId", - table: "ClientPostLogoutRedirectUris", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientProperties_ClientId", - table: "ClientProperties", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientRedirectUris_ClientId", - table: "ClientRedirectUris", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_Clients_ClientId", - table: "Clients", - column: "ClientId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ClientScopes_ClientId", - table: "ClientScopes", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientSecrets_ClientId", - table: "ClientSecrets", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityClaims_IdentityResourceId", - table: "IdentityClaims", - column: "IdentityResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityProperties_IdentityResourceId", - table: "IdentityProperties", - column: "IdentityResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityResources_Name", - table: "IdentityResources", - column: "Name", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ApiClaims"); - - migrationBuilder.DropTable( - name: "ApiProperties"); - - migrationBuilder.DropTable( - name: "ApiScopeClaims"); - - migrationBuilder.DropTable( - name: "ApiSecrets"); - - migrationBuilder.DropTable( - name: "ClientClaims"); - - migrationBuilder.DropTable( - name: "ClientCorsOrigins"); - - migrationBuilder.DropTable( - name: "ClientGrantTypes"); - - migrationBuilder.DropTable( - name: "ClientIdPRestrictions"); - - migrationBuilder.DropTable( - name: "ClientPostLogoutRedirectUris"); - - migrationBuilder.DropTable( - name: "ClientProperties"); - - migrationBuilder.DropTable( - name: "ClientRedirectUris"); - - migrationBuilder.DropTable( - name: "ClientScopes"); - - migrationBuilder.DropTable( - name: "ClientSecrets"); - - migrationBuilder.DropTable( - name: "IdentityClaims"); - - migrationBuilder.DropTable( - name: "IdentityProperties"); - - migrationBuilder.DropTable( - name: "ApiScopes"); - - migrationBuilder.DropTable( - name: "Clients"); - - migrationBuilder.DropTable( - name: "IdentityResources"); - - migrationBuilder.DropTable( - name: "ApiResources"); - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs deleted file mode 100644 index 7eb4d34e4..000000000 --- a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs +++ /dev/null @@ -1,909 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Identity.API.Migrations.ConfigurationDb -{ - [DbContext(typeof(ConfigurationDbContext))] - partial class ConfigurationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Enabled") - .HasColumnType("bit"); - - b.Property("LastAccessed") - .HasColumnType("datetime2"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiResources", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Emphasize") - .HasColumnType("bit"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiScopes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiScopeId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiScopeId"); - - b.ToTable("ApiScopeClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ApiResourceId") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("nvarchar(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiSecrets", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("int"); - - b.Property("AccessTokenLifetime") - .HasColumnType("int"); - - b.Property("AccessTokenType") - .HasColumnType("int"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("bit"); - - b.Property("AllowOfflineAccess") - .HasColumnType("bit"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("bit"); - - b.Property("AllowRememberConsent") - .HasColumnType("bit"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("bit"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("bit"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("int"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("bit"); - - b.Property("BackChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ClientClaimsPrefix") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ConsentLifetime") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("int"); - - b.Property("EnableLocalLogin") - .HasColumnType("bit"); - - b.Property("Enabled") - .HasColumnType("bit"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("bit"); - - b.Property("FrontChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("int"); - - b.Property("IncludeJwtId") - .HasColumnType("bit"); - - b.Property("LastAccessed") - .HasColumnType("datetime2"); - - b.Property("LogoUri") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("PairWiseSubjectSalt") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ProtocolType") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("int"); - - b.Property("RefreshTokenUsage") - .HasColumnType("int"); - - b.Property("RequireClientSecret") - .HasColumnType("bit"); - - b.Property("RequireConsent") - .HasColumnType("bit"); - - b.Property("RequirePkce") - .HasColumnType("bit"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("int"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.Property("UserCodeType") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("UserSsoLifetime") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("Clients", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Origin") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("nvarchar(150)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientCorsOrigins", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("GrantType") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientIdPRestrictions", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("PostLogoutRedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientPostLogoutRedirectUris", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("RedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClientId") - .HasColumnType("int"); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("nvarchar(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientSecrets", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("IdentityResourceId") - .HasColumnType("int"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityClaims", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Created") - .HasColumnType("datetime2"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Emphasize") - .HasColumnType("bit"); - - b.Property("Enabled") - .HasColumnType("bit"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("NonEditable") - .HasColumnType("bit"); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("bit"); - - b.Property("Updated") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("IdentityResources", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("IdentityResourceId") - .HasColumnType("int"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityProperties", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") - .WithMany("UserClaims") - .HasForeignKey("ApiScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiScope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.Designer.cs b/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.Designer.cs deleted file mode 100644 index 6e2567b4a..000000000 --- a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.Designer.cs +++ /dev/null @@ -1,108 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Identity.API.Migrations.PersistedGrantDb -{ - [DbContext(typeof(PersistedGrantDbContext))] - [Migration("20210813072513_InitialMigration")] - partial class InitialMigration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("nvarchar(max)"); - - b.Property("DeviceCode") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Expiration") - .IsRequired() - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("UserCode"); - - b.HasIndex("DeviceCode") - .IsUnique(); - - b.HasIndex("Expiration"); - - b.ToTable("DeviceCodes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("nvarchar(max)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Key"); - - b.HasIndex("Expiration"); - - b.HasIndex("SubjectId", "ClientId", "Type"); - - b.ToTable("PersistedGrants", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.cs b/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.cs deleted file mode 100644 index e81f8a197..000000000 --- a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/20210813072513_InitialMigration.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Identity.API.Migrations.PersistedGrantDb -{ - public partial class InitialMigration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DeviceCodes", - columns: table => new - { - UserCode = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - DeviceCode = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - SubjectId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - ClientId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - CreationTime = table.Column(type: "datetime2", nullable: false), - Expiration = table.Column(type: "datetime2", nullable: false), - Data = table.Column(type: "nvarchar(max)", maxLength: 50000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); - }); - - migrationBuilder.CreateTable( - name: "PersistedGrants", - columns: table => new - { - Key = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - SubjectId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - ClientId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - CreationTime = table.Column(type: "datetime2", nullable: false), - Expiration = table.Column(type: "datetime2", nullable: true), - Data = table.Column(type: "nvarchar(max)", maxLength: 50000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_PersistedGrants", x => x.Key); - }); - - migrationBuilder.CreateIndex( - name: "IX_DeviceCodes_DeviceCode", - table: "DeviceCodes", - column: "DeviceCode", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_DeviceCodes_Expiration", - table: "DeviceCodes", - column: "Expiration"); - - migrationBuilder.CreateIndex( - name: "IX_PersistedGrants_Expiration", - table: "PersistedGrants", - column: "Expiration"); - - migrationBuilder.CreateIndex( - name: "IX_PersistedGrants_SubjectId_ClientId_Type", - table: "PersistedGrants", - columns: new[] { "SubjectId", "ClientId", "Type" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DeviceCodes"); - - migrationBuilder.DropTable( - name: "PersistedGrants"); - } - } -} diff --git a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs b/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs deleted file mode 100644 index 769f60a5a..000000000 --- a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs +++ /dev/null @@ -1,106 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Identity.API.Migrations.PersistedGrantDb -{ - [DbContext(typeof(PersistedGrantDbContext))] - partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("nvarchar(max)"); - - b.Property("DeviceCode") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Expiration") - .IsRequired() - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("UserCode"); - - b.HasIndex("DeviceCode") - .IsUnique(); - - b.HasIndex("Expiration"); - - b.ToTable("DeviceCodes", (string)null); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("CreationTime") - .HasColumnType("datetime2"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("nvarchar(max)"); - - b.Property("Expiration") - .HasColumnType("datetime2"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Key"); - - b.HasIndex("Expiration"); - - b.HasIndex("SubjectId", "ClientId", "Type"); - - b.ToTable("PersistedGrants", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentInputModel.cs b/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentInputModel.cs deleted file mode 100644 index fd4e524cf..000000000 --- a/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentInputModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels -{ - public record ConsentInputModel - { - public string Button { get; init; } - public IEnumerable ScopesConsented { get; init; } - public bool RememberConsent { get; init; } - public string ReturnUrl { get; init; } - } -} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentViewModel.cs b/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentViewModel.cs deleted file mode 100644 index 6e9e46fe6..000000000 --- a/src/Services/Identity/Identity.API/Models/AccountViewModels/ConsentViewModel.cs +++ /dev/null @@ -1,61 +0,0 @@ -using IdentityServer4.Models; - -namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels -{ - public record ConsentViewModel : ConsentInputModel - { - public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, Resources resources) - { - RememberConsent = model?.RememberConsent ?? true; - ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(); - - ReturnUrl = returnUrl; - - ClientName = client.ClientName; - ClientUrl = client.ClientUri; - ClientLogoUrl = client.LogoUri; - AllowRememberConsent = client.AllowRememberConsent; - - IdentityScopes = resources.IdentityResources.Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray(); - ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray(); - } - - public string ClientName { get; init; } - public string ClientUrl { get; init; } - public string ClientLogoUrl { get; init; } - public bool AllowRememberConsent { get; init; } - - public IEnumerable IdentityScopes { get; init; } - public IEnumerable ResourceScopes { get; init; } - } - - public record ScopeViewModel - { - public ScopeViewModel(Scope scope, bool check) - { - Name = scope.Name; - DisplayName = scope.DisplayName; - Description = scope.Description; - Emphasize = scope.Emphasize; - Required = scope.Required; - Checked = check || scope.Required; - } - - public ScopeViewModel(IdentityResource identity, bool check) - { - Name = identity.Name; - DisplayName = identity.DisplayName; - Description = identity.Description; - Emphasize = identity.Emphasize; - Required = identity.Required; - Checked = check || identity.Required; - } - - public string Name { get; init; } - public string DisplayName { get; init; } - public string Description { get; init; } - public bool Emphasize { get; init; } - public bool Required { get; init; } - public bool Checked { get; init; } - } -} diff --git a/src/Services/Identity/Identity.API/Models/AccountViewModels/RedirectViewModel.cs b/src/Services/Identity/Identity.API/Models/AccountViewModels/RedirectViewModel.cs new file mode 100644 index 000000000..1693abb7d --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/AccountViewModels/RedirectViewModel.cs @@ -0,0 +1,7 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels +{ + public class RedirectViewModel + { + public string RedirectUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentInputModel.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentInputModel.cs new file mode 100644 index 000000000..6b0c22d8f --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentInputModel.cs @@ -0,0 +1,11 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ConsentInputModel + { + public string Button { get; set; } + public IEnumerable ScopesConsented { get; set; } + public bool RememberConsent { get; set; } + public string ReturnUrl { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentOptions.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentOptions.cs new file mode 100644 index 000000000..065b1825c --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentOptions.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ConsentOptions + { + public static bool EnableOfflineAccess = true; + public static string OfflineAccessDisplayName = "Offline Access"; + public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; + + public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; + public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentViewModel.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentViewModel.cs new file mode 100644 index 000000000..855e9a202 --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ConsentViewModel.cs @@ -0,0 +1,13 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ConsentViewModel : ConsentInputModel + { + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public bool AllowRememberConsent { get; set; } + + public IEnumerable IdentityScopes { get; set; } + public IEnumerable ApiScopes { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ProcessConsentResult.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ProcessConsentResult.cs new file mode 100644 index 000000000..88ef60e1a --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ProcessConsentResult.cs @@ -0,0 +1,15 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ProcessConsentResult + { + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + public Client Client { get; set; } + + public bool ShowView => ViewModel != null; + public ConsentViewModel ViewModel { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Models/ConsentViewModels/ScopeViewModel.cs b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ScopeViewModel.cs new file mode 100644 index 000000000..55c842dc9 --- /dev/null +++ b/src/Services/Identity/Identity.API/Models/ConsentViewModels/ScopeViewModel.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels +{ + public class ScopeViewModel + { + public string Value { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public bool Emphasize { get; set; } + public bool Required { get; set; } + public bool Checked { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Program.cs b/src/Services/Identity/Identity.API/Program.cs index 7c021c7a4..aa3d03a84 100644 --- a/src/Services/Identity/Identity.API/Program.cs +++ b/src/Services/Identity/Identity.API/Program.cs @@ -1,90 +1,63 @@ -string Namespace = typeof(Startup).Namespace; -string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); +var builder = WebApplication.CreateBuilder(args); -var configuration = GetConfiguration(); +builder.AddServiceDefaults(); -Log.Logger = CreateSerilogLogger(configuration); +builder.Services.AddControllersWithViews(); -try -{ - Log.Information("Configuring web host ({ApplicationContext})...", AppName); - var host = BuildWebHost(configuration, args); - - Log.Information("Applying migrations ({ApplicationContext})...", AppName); - host.MigrateDbContext((_, __) => { }) - .MigrateDbContext((context, services) => - { - var env = services.GetService(); - var logger = services.GetService>(); - var settings = services.GetService>(); +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityDB"))); - new ApplicationDbContextSeed() - .SeedAsync(context, env, logger, settings) - .Wait(); - }) - .MigrateDbContext((context, services) => - { - new ConfigurationDbContextSeed() - .SeedAsync(context, configuration) - .Wait(); - }); +builder.Services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); - Log.Information("Starting web host ({ApplicationContext})...", AppName); - host.Run(); - - return 0; -} -catch (Exception ex) +builder.Services.AddIdentityServer(options => { - Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); - return 1; -} -finally + options.IssuerUri = "null"; + options.Authentication.CookieLifetime = TimeSpan.FromHours(2); + + options.Events.RaiseErrorEvents = true; + options.Events.RaiseInformationEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseSuccessEvents = true; +}) +.AddInMemoryIdentityResources(Config.GetResources()) +.AddInMemoryApiScopes(Config.GetApiScopes()) +.AddInMemoryApiResources(Config.GetApis()) +.AddInMemoryClients(Config.GetClients(builder.Configuration)) +.AddAspNetIdentity() +.AddDeveloperSigningCredential(); // Not recommended for production - you need to store your key material somewhere secure + +builder.Services.AddHealthChecks() + .AddSqlServer(_ => + builder.Configuration.GetRequiredConnectionString("IdentityDB"), + name: "IdentityDB-check", + tags: new string[] { "IdentityDB" }); + +builder.Services.AddTransient(); +builder.Services.AddTransient, EFLoginService>(); +builder.Services.AddTransient(); + +var app = builder.Build(); + +app.UseServiceDefaults(); + +app.UseStaticFiles(); + +// This cookie policy fixes login issues with Chrome 80+ using HHTP +app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Lax }); +app.UseRouting(); +app.UseIdentityServer(); +app.UseAuthorization(); + +app.MapDefaultControllerRoute(); + +// Apply database migration automatically. Note that this approach is not +// recommended for production scenarios. Consider generating SQL scripts from +// migrations instead. +using (var scope = app.Services.CreateScope()) { - Log.CloseAndFlush(); + await SeedData.EnsureSeedData(scope, app.Configuration, app.Logger); } -IWebHost BuildWebHost(IConfiguration configuration, string[] args) => - WebHost.CreateDefaultBuilder(args) - .CaptureStartupErrors(false) - .ConfigureAppConfiguration(x => x.AddConfiguration(configuration)) - .UseStartup() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseSerilog() - .Build(); - -Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) -{ - var seqServerUrl = configuration["Serilog:SeqServerUrl"]; - var logstashUrl = configuration["Serilog:LogstashgUrl"]; - return new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithProperty("ApplicationContext", AppName) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) - .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://localhost:8080" : logstashUrl) - .ReadFrom.Configuration(configuration) - .CreateLogger(); -} - -IConfiguration GetConfiguration() -{ - var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddEnvironmentVariables(); - - var config = builder.Build(); - - if (config.GetValue("UseVault", false)) - { - TokenCredential credential = new ClientSecretCredential( - config["Vault:TenantId"], - config["Vault:ClientId"], - config["Vault:ClientSecret"]); - builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential); - } - - return builder.Build(); -} \ No newline at end of file +await app.RunAsync(); diff --git a/src/Services/Identity/Identity.API/Properties/launchSettings.json b/src/Services/Identity/Identity.API/Properties/launchSettings.json index e52e9f99c..485a5f313 100644 --- a/src/Services/Identity/Identity.API/Properties/launchSettings.json +++ b/src/Services/Identity/Identity.API/Properties/launchSettings.json @@ -1,25 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:54010/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "http://localhost:55105", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "eShopOnContainers.Identity": { + "Identity.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:55105", + "applicationUrl": "http://localhost:5223", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Services/Identity/Identity.API/Properties/serviceDependencies.json b/src/Services/Identity/Identity.API/Properties/serviceDependencies.json new file mode 100644 index 000000000..718987b23 --- /dev/null +++ b/src/Services/Identity/Identity.API/Properties/serviceDependencies.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "mssql1": { + "type": "mssql", + "connectionId": "ConnectionString", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Properties/serviceDependencies.local.json b/src/Services/Identity/Identity.API/Properties/serviceDependencies.local.json new file mode 100644 index 000000000..cae5e3931 --- /dev/null +++ b/src/Services/Identity/Identity.API/Properties/serviceDependencies.local.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "mssql1": { + "serviceConnectorResourceId": "", + "containerPorts": "1433:1433", + "secretStore": "LocalSecretsFile", + "containerName": "identity-sql", + "containerImage": "mcr.microsoft.com/mssql/server:2019-latest", + "type": "mssql.container", + "connectionId": "ConnectionString", + "dynamicId": null + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/AccountController.cs b/src/Services/Identity/Identity.API/Quickstart/Account/AccountController.cs new file mode 100644 index 000000000..e0aef7b95 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/AccountController.cs @@ -0,0 +1,334 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI +{ + [SecurityHeaders] + [AllowAnonymous] + public class AccountController : Controller + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IAuthenticationSchemeProvider _schemeProvider; + private readonly IAuthenticationHandlerProvider _handlerProvider; + private readonly IEventService _events; + + public AccountController( + UserManager userManager, + SignInManager signInManager, + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IAuthenticationSchemeProvider schemeProvider, + IAuthenticationHandlerProvider handlerProvider, + IEventService events) + { + _userManager = userManager; + _signInManager = signInManager; + _interaction = interaction; + _clientStore = clientStore; + _schemeProvider = schemeProvider; + _handlerProvider = handlerProvider; + _events = events; + } + + /// + /// Entry point into the login workflow + /// + [HttpGet] + public async Task Login(string returnUrl) + { + // build a model so we know what to show on the login page + var vm = await BuildLoginViewModelAsync(returnUrl); + + ViewData["ReturnUrl"] = returnUrl; + + if (vm.IsExternalLoginOnly) + { + // we only have one option for logging in and it's an external provider + return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl }); + } + + return View(vm); + } + + /// + /// Handle postback from username/password login + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(LoginInputModel model, string button) + { + // check if we are in the context of an authorization request + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + + // the user clicked the "cancel" button + if (button != "login") + { + if (context != null) + { + // if the user cancels, send a result back into IdentityServer as if they + // denied the consent (even if this client does not require consent). + // this will send back an access denied OIDC error response to the client. + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + return Redirect(model.ReturnUrl); + } + else + { + // since we don't have a valid context, then we just go back to the home page + return Redirect("~/"); + } + } + + if (ModelState.IsValid) + { + var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true); + if (result.Succeeded) + { + var user = await _userManager.FindByNameAsync(model.Username); + await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return Redirect(model.ReturnUrl); + } + + // request for a local page + if (Url.IsLocalUrl(model.ReturnUrl)) + { + return Redirect(model.ReturnUrl); + } + else if (string.IsNullOrEmpty(model.ReturnUrl)) + { + return Redirect("~/"); + } + else + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + } + + await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId: context?.Client.ClientId)); + ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); + } + + // something went wrong, show form with error + var vm = await BuildLoginViewModelAsync(model); + + ViewData["ReturnUrl"] = model.ReturnUrl; + + return View(vm); + } + + + /// + /// Show logout page + /// + [HttpGet] + public async Task Logout(string logoutId) + { + // build a model so the logout page knows what to display + var vm = await BuildLogoutViewModelAsync(logoutId); + + if (vm.ShowLogoutPrompt == false) + { + // if the request for logout was properly authenticated from IdentityServer, then + // we don't need to show the prompt and can just log the user out directly. + return await Logout(vm); + } + + return View(vm); + } + + /// + /// Handle logout page postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Logout(LogoutInputModel model) + { + // build a model so the logged out page knows what to display + var vm = await BuildLoggedOutViewModelAsync(model.LogoutId); + + if (User?.Identity.IsAuthenticated == true) + { + // delete local authentication cookie + await _signInManager.SignOutAsync(); + + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + } + + // check if we need to trigger sign-out at an upstream identity provider + if (vm.TriggerExternalSignout) + { + // build a return URL so the upstream provider will redirect back + // to us after the user has logged out. this allows us to then + // complete our single sign-out processing. + string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); + + // this triggers a redirect to the external provider for sign-out + return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); + } + + return View("LoggedOut", vm); + } + + [HttpGet] + public IActionResult AccessDenied() + { + return View(); + } + + + /*****************************************/ + /* helper APIs for the AccountController */ + /*****************************************/ + private async Task BuildLoginViewModelAsync(string returnUrl) + { + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) + { + var local = context.IdP == IdentityServerConstants.LocalIdentityProvider; + + // this is meant to short circuit the UI and only trigger the one external IdP + var vm = new LoginViewModel + { + EnableLocalLogin = local, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + }; + + if (!local) + { + vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; + } + + return vm; + } + + var schemes = await _schemeProvider.GetAllSchemesAsync(); + + var providers = schemes + .Where(x => x.DisplayName != null) + .Select(x => new ExternalProvider + { + DisplayName = x.DisplayName ?? x.Name, + AuthenticationScheme = x.Name + }).ToList(); + + var allowLocal = true; + if (context?.Client.ClientId != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); + if (client != null) + { + allowLocal = client.EnableLocalLogin; + + if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) + { + providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); + } + } + } + + return new LoginViewModel + { + AllowRememberLogin = AccountOptions.AllowRememberLogin, + EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + ExternalProviders = providers.ToArray() + }; + } + + private async Task BuildLoginViewModelAsync(LoginInputModel model) + { + var vm = await BuildLoginViewModelAsync(model.ReturnUrl); + vm.Username = model.Username; + vm.RememberLogin = model.RememberLogin; + return vm; + } + + private async Task BuildLogoutViewModelAsync(string logoutId) + { + var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; + + if (User?.Identity.IsAuthenticated != true) + { + // if the user is not authenticated, then just show logged out page + vm.ShowLogoutPrompt = false; + return vm; + } + + var context = await _interaction.GetLogoutContextAsync(logoutId); + if (context?.ShowSignoutPrompt == false) + { + // it's safe to automatically sign-out + vm.ShowLogoutPrompt = false; + return vm; + } + + // show the logout prompt. this prevents attacks where the user + // is automatically signed out by another malicious web page. + return vm; + } + + private async Task BuildLoggedOutViewModelAsync(string logoutId) + { + // get context information (client name, post logout redirect URI and iframe for federated signout) + var logout = await _interaction.GetLogoutContextAsync(logoutId); + + var vm = new LoggedOutViewModel + { + AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, + PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, + ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, + SignOutIframeUrl = logout?.SignOutIFrameUrl, + LogoutId = logoutId + }; + + if (User?.Identity.IsAuthenticated == true) + { + var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider) + { + var handler = await _handlerProvider.GetHandlerAsync(HttpContext, idp); + if (handler is IAuthenticationSignOutHandler) + { + if (vm.LogoutId == null) + { + // if there's no current logout context, we need to create one + // this captures necessary info from the current logged in user + // before we signout and redirect away to the external IdP for signout + vm.LogoutId = await _interaction.CreateLogoutContextAsync(); + } + + vm.ExternalAuthenticationScheme = idp; + } + } + } + + return vm; + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/AccountOptions.cs b/src/Services/Identity/Identity.API/Quickstart/Account/AccountOptions.cs new file mode 100644 index 000000000..00ef16bc0 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/AccountOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +public class AccountOptions +{ + public static bool AllowLocalLogin = true; + public static bool AllowRememberLogin = true; + public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); + + public static bool ShowLogoutPrompt = false; + public static bool AutomaticRedirectAfterSignOut = true; + + public static string InvalidCredentialsErrorMessage = "Invalid username or password"; +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/ExternalController.cs b/src/Services/Identity/Identity.API/Quickstart/Account/ExternalController.cs new file mode 100644 index 000000000..5abc2fa3b --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/ExternalController.cs @@ -0,0 +1,238 @@ +namespace IdentityServerHost.Quickstart.UI; + +[SecurityHeaders] +[AllowAnonymous] +public class ExternalController : Controller +{ + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IEventService _events; + private readonly ILogger _logger; + + public ExternalController( + UserManager userManager, + SignInManager signInManager, + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IEventService events, + ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _interaction = interaction; + _clientStore = clientStore; + _events = events; + _logger = logger; + } + + /// + /// initiate roundtrip to external authentication provider + /// + [HttpGet] + public IActionResult Challenge(string scheme, string returnUrl) + { + if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; + + // validate returnUrl - either it is a valid OIDC URL or back to a local page + if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false) + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + + // start challenge and roundtrip the return URL and scheme + var props = new AuthenticationProperties + { + RedirectUri = Url.Action(nameof(Callback)), + Items = + { + { "returnUrl", returnUrl }, + { "scheme", scheme }, + } + }; + + return Challenge(props, scheme); + + } + + /// + /// Post processing of external authentication + /// + [HttpGet] + public async Task Callback() + { + // read external identity from the temporary cookie + var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + if (result?.Succeeded != true) + { + throw new Exception("External authentication error"); + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}"); + _logger.LogDebug("External claims: {@claims}", externalClaims); + } + + // lookup our user and external provider info + var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result); + if (user == null) + { + // this might be where you might initiate a custom workflow for user registration + // in this sample we don't show how that would be done, as our sample implementation + // simply auto-provisions new external user + user = await AutoProvisionUserAsync(provider, providerUserId, claims); + } + + // this allows us to collect any additional claims or properties + // for the specific protocols used and store them in the local auth cookie. + // this is typically used to store data needed for signout from those protocols. + var additionalLocalClaims = new List(); + var localSignInProps = new AuthenticationProperties(); + ProcessLoginCallback(result, additionalLocalClaims, localSignInProps); + + // issue authentication cookie for user + // we must issue the cookie maually, and can't use the SignInManager because + // it doesn't expose an API to issue additional claims from the login workflow + var principal = await _signInManager.CreateUserPrincipalAsync(user); + additionalLocalClaims.AddRange(principal.Claims); + var name = principal.FindFirst(JwtClaimTypes.Name)?.Value ?? user.Id; + + var isuser = new IdentityServerUser(user.Id) + { + DisplayName = name, + IdentityProvider = provider, + AdditionalClaims = additionalLocalClaims + }; + + await HttpContext.SignInAsync(isuser, localSignInProps); + + // delete temporary cookie used during external authentication + await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + + // retrieve return URL + var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; + + // check if external login is in the context of an OIDC request + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, name, true, context?.Client.ClientId)); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", returnUrl); + } + } + + return Redirect(returnUrl); + } + + private async Task<(ApplicationUser user, string provider, string providerUserId, IEnumerable claims)> + FindUserFromExternalProviderAsync(AuthenticateResult result) + { + var externalUser = result.Principal; + + // try to determine the unique id of the external user (issued by the provider) + // the most common claim type for that are the sub claim and the NameIdentifier + // depending on the external provider, some other claim type might be used + var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? + externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? + throw new Exception("Unknown userid"); + + // remove the user id claim so we don't include it as an extra claim if/when we provision the user + var claims = externalUser.Claims.ToList(); + claims.Remove(userIdClaim); + + var provider = result.Properties.Items["scheme"]; + var providerUserId = userIdClaim.Value; + + // find external user + var user = await _userManager.FindByLoginAsync(provider, providerUserId); + + return (user, provider, providerUserId, claims); + } + + private async Task AutoProvisionUserAsync(string provider, string providerUserId, IEnumerable claims) + { + // create a list of claims that we want to transfer into our store + var filtered = new List(); + + // user's display name + var name = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Name)?.Value ?? + claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; + if (name != null) + { + filtered.Add(new Claim(JwtClaimTypes.Name, name)); + } + else + { + var first = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value ?? + claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value; + var last = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value ?? + claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value; + if (first != null && last != null) + { + filtered.Add(new Claim(JwtClaimTypes.Name, first + " " + last)); + } + else if (first != null) + { + filtered.Add(new Claim(JwtClaimTypes.Name, first)); + } + else if (last != null) + { + filtered.Add(new Claim(JwtClaimTypes.Name, last)); + } + } + + // email + var email = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Email)?.Value ?? + claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; + if (email != null) + { + filtered.Add(new Claim(JwtClaimTypes.Email, email)); + } + + var user = new ApplicationUser + { + UserName = Guid.NewGuid().ToString(), + }; + var identityResult = await _userManager.CreateAsync(user); + if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); + + if (filtered.Any()) + { + identityResult = await _userManager.AddClaimsAsync(user, filtered); + if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); + } + + identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerUserId, provider)); + if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); + + return user; + } + + // if the external login is OIDC-based, there are certain things we need to preserve to make logout work + // this will be different for WS-Fed, SAML2p or other protocols + private void ProcessLoginCallback(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) + { + // if the external system sent a session id claim, copy it over + // so we can use it for single sign-out + var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); + if (sid != null) + { + localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); + } + + // if the external provider issued an id_token, we'll keep it for signout + var idToken = externalResult.Properties.GetTokenValue("id_token"); + if (idToken != null) + { + localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } }); + } + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/ExternalProvider.cs b/src/Services/Identity/Identity.API/Quickstart/Account/ExternalProvider.cs new file mode 100644 index 000000000..c92594f4e --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/ExternalProvider.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +public class ExternalProvider +{ + public string DisplayName { get; set; } + public string AuthenticationScheme { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LoggedOutViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LoggedOutViewModel.cs new file mode 100644 index 000000000..7e55f2cb6 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LoggedOutViewModel.cs @@ -0,0 +1,18 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LoggedOutViewModel +{ + public string PostLogoutRedirectUri { get; set; } + public string ClientName { get; set; } + public string SignOutIframeUrl { get; set; } + + public bool AutomaticRedirectAfterSignOut { get; set; } + + public string LogoutId { get; set; } + public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; + public string ExternalAuthenticationScheme { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LoginInputModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LoginInputModel.cs new file mode 100644 index 000000000..813ba8fc1 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LoginInputModel.cs @@ -0,0 +1,15 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LoginInputModel +{ + [Required] + public string Username { get; set; } + [Required] + public string Password { get; set; } + public bool RememberLogin { get; set; } + public string ReturnUrl { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs new file mode 100644 index 000000000..f539c70e9 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LoginViewModel.cs @@ -0,0 +1,17 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LoginViewModel : LoginInputModel +{ + public bool AllowRememberLogin { get; set; } = true; + public bool EnableLocalLogin { get; set; } = true; + + public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); + public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !string.IsNullOrWhiteSpace(x.DisplayName)); + + public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; + public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LogoutInputModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LogoutInputModel.cs new file mode 100644 index 000000000..2fd5d8c8f --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LogoutInputModel.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LogoutInputModel +{ + public string LogoutId { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/LogoutViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/LogoutViewModel.cs new file mode 100644 index 000000000..841289b70 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/LogoutViewModel.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class LogoutViewModel : LogoutInputModel +{ + public bool ShowLogoutPrompt { get; set; } = true; +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Account/RedirectViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Account/RedirectViewModel.cs new file mode 100644 index 000000000..e5005f4c2 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Account/RedirectViewModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + + +namespace IdentityServerHost.Quickstart.UI; + +public class RedirectViewModel +{ + public string RedirectUrl { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs new file mode 100644 index 000000000..2cdf323ec --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentController.cs @@ -0,0 +1,247 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +/// +/// This controller processes the consent UI +/// +[SecurityHeaders] +[Authorize] +public class ConsentController : Controller +{ + private readonly IIdentityServerInteractionService _interaction; + private readonly IEventService _events; + private readonly ILogger _logger; + + public ConsentController( + IIdentityServerInteractionService interaction, + IEventService events, + ILogger logger) + { + _interaction = interaction; + _events = events; + _logger = logger; + } + + /// + /// Shows the consent screen + /// + /// + /// + [HttpGet] + public async Task Index(string returnUrl) + { + var vm = await BuildViewModelAsync(returnUrl); + if (vm != null) + { + return View("Index", vm); + } + + return View("Error"); + } + + /// + /// Handles the consent screen postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Index(ConsentInputModel model) + { + var result = await ProcessConsent(model); + + if (result.IsRedirect) + { + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (context?.IsNativeClient() == true) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", result.RedirectUri); + } + + return Redirect(result.RedirectUri); + } + + if (result.HasValidationError) + { + ModelState.AddModelError(string.Empty, result.ValidationError); + } + + if (result.ShowView) + { + return View("Index", result.ViewModel); + } + + return View("Error"); + } + + /*****************************************/ + /* helper APIs for the ConsentController */ + /*****************************************/ + private async Task ProcessConsent(ConsentInputModel model) + { + var result = new ProcessConsentResult(); + + // validate return url is still valid + var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model?.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model?.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.GrantConsentAsync(request, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (request != null) + { + return CreateConsentViewModel(model, returnUrl, request); + } + else + { + _logger.LogError("No consent request matching request: {0}", returnUrl); + } + + return null; + } + + private ConsentViewModel CreateConsentViewModel( + ConsentInputModel model, string returnUrl, + AuthorizationRequest request) + { + var vm = new ConsentViewModel + { + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + Description = model?.Description, + + ReturnUrl = returnUrl, + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach (var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + var displayName = apiScope.DisplayName ?? apiScope.Name; + if (!string.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) + { + displayName += ":" + parsedScopeValue.ParsedParameter; + } + + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + DisplayName = displayName, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentInputModel.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentInputModel.cs new file mode 100644 index 000000000..fc661024e --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentInputModel.cs @@ -0,0 +1,14 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class ConsentInputModel +{ + public string Button { get; set; } + public IEnumerable ScopesConsented { get; set; } + public bool RememberConsent { get; set; } + public string ReturnUrl { get; set; } + public string Description { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentOptions.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentOptions.cs new file mode 100644 index 000000000..4a689278c --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentOptions.cs @@ -0,0 +1,15 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class ConsentOptions +{ + public static bool EnableOfflineAccess = true; + public static string OfflineAccessDisplayName = "Offline Access"; + public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; + + public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; + public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentViewModel.cs new file mode 100644 index 000000000..0603d7836 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ConsentViewModel.cs @@ -0,0 +1,16 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class ConsentViewModel : ConsentInputModel +{ + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public bool AllowRememberConsent { get; set; } + + public IEnumerable IdentityScopes { get; set; } + public IEnumerable ApiScopes { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ProcessConsentResult.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ProcessConsentResult.cs new file mode 100644 index 000000000..a7dd8bb7e --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ProcessConsentResult.cs @@ -0,0 +1,17 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +public class ProcessConsentResult +{ + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + public Client Client { get; set; } + + public bool ShowView => ViewModel != null; + public ConsentViewModel ViewModel { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Consent/ScopeViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Consent/ScopeViewModel.cs new file mode 100644 index 000000000..c052253c2 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Consent/ScopeViewModel.cs @@ -0,0 +1,15 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class ScopeViewModel +{ + public string Value { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public bool Emphasize { get; set; } + public bool Required { get; set; } + public bool Checked { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationInputModel.cs b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationInputModel.cs new file mode 100644 index 000000000..8e6403960 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationInputModel.cs @@ -0,0 +1,10 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class DeviceAuthorizationInputModel : ConsentInputModel +{ + public string UserCode { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationViewModel.cs new file mode 100644 index 000000000..230413f26 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceAuthorizationViewModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class DeviceAuthorizationViewModel : ConsentViewModel +{ + public string UserCode { get; set; } + public bool ConfirmUserCode { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Device/DeviceController.cs b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceController.cs new file mode 100644 index 000000000..5eb55caad --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Device/DeviceController.cs @@ -0,0 +1,214 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +[Authorize] +[SecurityHeaders] +public class DeviceController : Controller +{ + private readonly IDeviceFlowInteractionService _interaction; + private readonly IEventService _events; + private readonly IOptions _options; + private readonly ILogger _logger; + + public DeviceController( + IDeviceFlowInteractionService interaction, + IEventService eventService, + IOptions options, + ILogger logger) + { + _interaction = interaction; + _events = eventService; + _options = options; + _logger = logger; + } + + [HttpGet] + public async Task Index() + { + string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter; + string userCode = Request.Query[userCodeParamName]; + if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture"); + + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + vm.ConfirmUserCode = true; + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UserCodeCapture(string userCode) + { + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Callback(DeviceAuthorizationInputModel model) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + + var result = await ProcessConsent(model); + if (result.HasValidationError) return View("Error"); + + return View("Success"); + } + + private async Task ProcessConsent(DeviceAuthorizationInputModel model) + { + var result = new ProcessConsentResult(); + + var request = await _interaction.GetAuthorizationContextAsync(model.UserCode); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.HandleRequestAsync(model.UserCode, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.UserCode, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(userCode); + if (request != null) + { + return CreateConsentViewModel(userCode, model, request); + } + + return null; + } + + private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request) + { + var vm = new DeviceAuthorizationViewModel + { + UserCode = userCode, + Description = model?.Description, + + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach (var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + DisplayName = apiScope.DisplayName ?? apiScope.Name, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsController.cs b/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsController.cs new file mode 100644 index 000000000..9668eec1a --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsController.cs @@ -0,0 +1,22 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +[SecurityHeaders] +[Authorize] +public class DiagnosticsController : Controller +{ + public async Task Index() + { + var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; + if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) + { + return NotFound(); + } + + var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync()); + return View(model); + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsViewModel.cs new file mode 100644 index 000000000..6f87fd1d7 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Diagnostics/DiagnosticsViewModel.cs @@ -0,0 +1,28 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Text; +using System.Text.Json; + +namespace IdentityServerHost.Quickstart.UI; + +public class DiagnosticsViewModel +{ + public DiagnosticsViewModel(AuthenticateResult result) + { + AuthenticateResult = result; + + if (result.Properties.Items.ContainsKey("client_list")) + { + var encoded = result.Properties.Items["client_list"]; + var bytes = Base64Url.Decode(encoded); + var value = Encoding.UTF8.GetString(bytes); + + Clients = JsonSerializer.Deserialize(value); + } + } + + public AuthenticateResult AuthenticateResult { get; } + public IEnumerable Clients { get; } = new List(); +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Extensions.cs b/src/Services/Identity/Identity.API/Quickstart/Extensions.cs new file mode 100644 index 000000000..4dbaf5385 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Extensions.cs @@ -0,0 +1,22 @@ +namespace IdentityServerHost.Quickstart.UI; + +public static class Extensions +{ + /// + /// Checks if the redirect URI is for a native client. + /// + /// + public static bool IsNativeClient(this AuthorizationRequest context) + { + return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) + && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); + } + + public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri) + { + controller.HttpContext.Response.StatusCode = 200; + controller.HttpContext.Response.Headers["Location"] = ""; + + return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri }); + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsController.cs b/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsController.cs new file mode 100644 index 000000000..3045c44d5 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsController.cs @@ -0,0 +1,85 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +/// +/// This sample controller allows a user to revoke grants given to clients +/// +[SecurityHeaders] +[Authorize] +public class GrantsController : Controller +{ + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clients; + private readonly IResourceStore _resources; + private readonly IEventService _events; + + public GrantsController(IIdentityServerInteractionService interaction, + IClientStore clients, + IResourceStore resources, + IEventService events) + { + _interaction = interaction; + _clients = clients; + _resources = resources; + _events = events; + } + + /// + /// Show list of grants + /// + [HttpGet] + public async Task Index() + { + return View("Index", await BuildViewModelAsync()); + } + + /// + /// Handle postback to revoke a client + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Revoke(string clientId) + { + await _interaction.RevokeUserConsentAsync(clientId); + await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); + + return RedirectToAction("Index"); + } + + private async Task BuildViewModelAsync() + { + var grants = await _interaction.GetAllUserGrantsAsync(); + + var list = new List(); + foreach (var grant in grants) + { + var client = await _clients.FindClientByIdAsync(grant.ClientId); + if (client != null) + { + var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); + + var item = new GrantViewModel() + { + ClientId = client.ClientId, + ClientName = client.ClientName ?? client.ClientId, + ClientLogoUrl = client.LogoUri, + ClientUrl = client.ClientUri, + Description = grant.Description, + Created = grant.CreationTime, + Expires = grant.Expiration, + IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), + ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray() + }; + + list.Add(item); + } + } + + return new GrantsViewModel + { + Grants = list + }; + } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsViewModel.cs new file mode 100644 index 000000000..991d27967 --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Grants/GrantsViewModel.cs @@ -0,0 +1,23 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class GrantsViewModel +{ + public IEnumerable Grants { get; set; } +} + +public class GrantViewModel +{ + public string ClientId { get; set; } + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public string Description { get; set; } + public DateTime Created { get; set; } + public DateTime? Expires { get; set; } + public IEnumerable IdentityGrantNames { get; set; } + public IEnumerable ApiGrantNames { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Home/ErrorViewModel.cs b/src/Services/Identity/Identity.API/Quickstart/Home/ErrorViewModel.cs new file mode 100644 index 000000000..e1cfa86cb --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Home/ErrorViewModel.cs @@ -0,0 +1,18 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI; + +public class ErrorViewModel +{ + public ErrorViewModel() + { + } + + public ErrorViewModel(string error) + { + Error = new ErrorMessage { Error = error }; + } + + public ErrorMessage Error { get; set; } +} diff --git a/src/Services/Identity/Identity.API/Quickstart/Home/HomeController.cs b/src/Services/Identity/Identity.API/Quickstart/Home/HomeController.cs new file mode 100644 index 000000000..27f4d73cc --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/Home/HomeController.cs @@ -0,0 +1,59 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace IdentityServerHost.Quickstart.UI +{ + [SecurityHeaders] + [AllowAnonymous] + public class HomeController : Controller + { + private readonly IIdentityServerInteractionService _interaction; + private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; + + public HomeController( + IIdentityServerInteractionService interaction, + IWebHostEnvironment environment, + ILogger logger) + { + _interaction = interaction; + _environment = environment; + _logger = logger; + } + + public IActionResult Index() + { + if (_environment.IsDevelopment()) + { + // only show in development + return View(); + } + + _logger.LogInformation("Homepage is disabled in production. Returning 404."); + return NotFound(); + } + + /// + /// Shows the error page + /// + public async Task Error(string errorId) + { + var vm = new ErrorViewModel(); + + // retrieve error details from identityserver + var message = await _interaction.GetErrorContextAsync(errorId); + if (message != null) + { + vm.Error = message; + + if (!_environment.IsDevelopment()) + { + // only show in development + message.ErrorDescription = null; + } + } + + return View("Error", vm); + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Quickstart/SecurityHeadersAttribute.cs b/src/Services/Identity/Identity.API/Quickstart/SecurityHeadersAttribute.cs new file mode 100644 index 000000000..df926beea --- /dev/null +++ b/src/Services/Identity/Identity.API/Quickstart/SecurityHeadersAttribute.cs @@ -0,0 +1,52 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI; + +public class SecurityHeadersAttribute : ActionFilterAttribute +{ + public override void OnResultExecuting(ResultExecutingContext context) + { + var result = context.Result; + if (result is ViewResult) + { + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) + { + context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) + { + context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; + // also consider adding upgrade-insecure-requests once you have HTTPS in place for production + //csp += "upgrade-insecure-requests;"; + // also an example if you need client images to be displayed from twitter + // csp += "img-src 'self' https://pbs.twimg.com;"; + + // once for standards compliant browsers + if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); + } + // and once again for IE + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + var referrer_policy = "no-referrer"; + if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) + { + context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); + } + } + } +} diff --git a/src/Services/Identity/Identity.API/SeedData.cs b/src/Services/Identity/Identity.API/SeedData.cs new file mode 100644 index 000000000..fa8365f9e --- /dev/null +++ b/src/Services/Identity/Identity.API/SeedData.cs @@ -0,0 +1,112 @@ +namespace Microsoft.eShopOnContainers.Services.Identity.API; + +public class SeedData +{ + public static async Task EnsureSeedData(IServiceScope scope, IConfiguration configuration, ILogger logger) + { + var retryPolicy = CreateRetryPolicy(configuration, logger); + var context = scope.ServiceProvider.GetRequiredService(); + + await retryPolicy.ExecuteAsync(async () => + { + await context.Database.MigrateAsync(); + + var userMgr = scope.ServiceProvider.GetRequiredService>(); + var alice = await userMgr.FindByNameAsync("alice"); + + if (alice == null) + { + alice = new ApplicationUser + { + UserName = "alice", + Email = "AliceSmith@email.com", + EmailConfirmed = true, + CardHolderName = "Alice Smith", + CardNumber = "4012888888881881", + CardType = 1, + City = "Redmond", + Country = "U.S.", + Expiration = "12/24", + Id = Guid.NewGuid().ToString(), + LastName = "Smith", + Name = "Alice", + PhoneNumber = "1234567890", + ZipCode = "98052", + State = "WA", + Street = "15703 NE 61st Ct", + SecurityNumber = "123" + }; + + var result = userMgr.CreateAsync(alice, "Pass123$").Result; + + if (!result.Succeeded) + { + throw new Exception(result.Errors.First().Description); + } + + logger.LogDebug("alice created"); + } + else + { + logger.LogDebug("alice already exists"); + } + + var bob = await userMgr.FindByNameAsync("bob"); + + if (bob == null) + { + bob = new ApplicationUser + { + UserName = "bob", + Email = "BobSmith@email.com", + EmailConfirmed = true, + CardHolderName = "Bob Smith", + CardNumber = "4012888888881881", + CardType = 1, + City = "Redmond", + Country = "U.S.", + Expiration = "12/24", + Id = Guid.NewGuid().ToString(), + LastName = "Smith", + Name = "Bob", + PhoneNumber = "1234567890", + ZipCode = "98052", + State = "WA", + Street = "15703 NE 61st Ct", + SecurityNumber = "456" + }; + + var result = await userMgr.CreateAsync(bob, "Pass123$"); + + if (!result.Succeeded) + { + throw new Exception(result.Errors.First().Description); + } + + logger.LogDebug("bob created"); + } + else + { + logger.LogDebug("bob already exists"); + } + }); + } + + private static AsyncPolicy CreateRetryPolicy(IConfiguration configuration, Microsoft.Extensions.Logging.ILogger logger) + { + var retryMigrations = false; + bool.TryParse(configuration["RetryMigrations"], out retryMigrations); + + // Only use a retry policy if configured to do so. + // When running in an orchestrator/K8s, it will take care of restarting failed services. + if (retryMigrations) + { + return Policy.Handle(). + WaitAndRetryForeverAsync( + sleepDurationProvider: retry => TimeSpan.FromSeconds(5), + onRetry: (exception, retry, timeSpan) => logger.LogWarning(exception, "Error migrating database (retry attempt {retry})", retry)); + } + + return Policy.NoOpAsync(); + } +} diff --git a/src/Services/Identity/Identity.API/Setup/Users.csv b/src/Services/Identity/Identity.API/Setup/Users.csv deleted file mode 100644 index a0955884b..000000000 --- a/src/Services/Identity/Identity.API/Setup/Users.csv +++ /dev/null @@ -1,2 +0,0 @@ -CardHolderName,CardNumber,CardType,City,Country,Email,Expiration,LastName,Name,PhoneNumber,UserName,ZipCode,State,Street,SecurityNumber,NormalizedEmail,NormalizedUserName,Password -DemoUser,4012888888881881,1,Redmond,U.S.,demouser@microsoft.com,12/25,DemoLastName,DemoUser,1234567890,demouser@microsoft.com,98052,WA,15703 NE 61st Ct,535,DEMOUSER@MICROSOFT.COM,DEMOUSER@MICROSOFT.COM,Pass@word1 \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Setup/images.zip b/src/Services/Identity/Identity.API/Setup/images.zip deleted file mode 100644 index 1b901d3af..000000000 Binary files a/src/Services/Identity/Identity.API/Setup/images.zip and /dev/null differ diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs deleted file mode 100644 index 43662acdd..000000000 --- a/src/Services/Identity/Identity.API/Startup.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Microsoft.AspNetCore.DataProtection; - -namespace Microsoft.eShopOnContainers.Services.Identity.API -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public IServiceProvider ConfigureServices(IServiceCollection services) - { - RegisterAppInsights(services); - - // Add framework services. - services.AddDbContext(options => - options.UseSqlServer(Configuration["ConnectionString"], - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - })); - - services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - services.Configure(Configuration); - - if (Configuration.GetValue("IsClusterEnv") == bool.TrueString) - { - services.AddDataProtection(opts => - { - opts.ApplicationDiscriminator = "eshop.identity"; - }) - .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys"); - } - - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddSqlServer(Configuration["ConnectionString"], - name: "IdentityDB-check", - tags: new string[] { "IdentityDB" }); - - services.AddTransient, EFLoginService>(); - services.AddTransient(); - - var connectionString = Configuration["ConnectionString"]; - var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; - - // Adds IdentityServer - services.AddIdentityServer(x => - { - x.IssuerUri = "null"; - x.Authentication.CookieLifetime = TimeSpan.FromHours(2); - }) - .AddDevspacesIfNeeded(Configuration.GetValue("EnableDevspaces", false)) - .AddSigningCredential(Certificate.Get()) - .AddAspNetIdentity() - .AddConfigurationStore(options => - { - options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(migrationsAssembly); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); - }) - .AddOperationalStore(options => - { - options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(migrationsAssembly); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); - }) - .Services.AddTransient(); - - //services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); - services.AddControllers(); - services.AddControllersWithViews(); - services.AddRazorPages(); - - var container = new ContainerBuilder(); - container.Populate(services); - - return new AutofacServiceProvider(container.Build()); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - //loggerFactory.AddConsole(Configuration.GetSection("Logging")); - //loggerFactory.AddDebug(); - //loggerFactory.AddAzureWebAppDiagnostics(); - //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - } - - var pathBase = Configuration["PATH_BASE"]; - if (!string.IsNullOrEmpty(pathBase)) - { - //loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); - app.UsePathBase(pathBase); - } - - app.UseStaticFiles(); - - // Make work identity server redirections in Edge and lastest versions of browsers. WARN: Not valid in a production environment. - app.Use(async (context, next) => - { - context.Response.Headers.Add("Content-Security-Policy", "script-src 'unsafe-inline'"); - await next(); - }); - - app.UseForwardedHeaders(); - // Adds IdentityServer - app.UseIdentityServer(); - - // Fix a problem with chrome. Chrome enabled a new feature "Cookies without SameSite must be secure", - // the cookies should be expired from https, but in eShop, the internal communication in aks and docker compose is http. - // To avoid this problem, the policy of cookies should be in Lax mode. - app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = AspNetCore.Http.SameSiteMode.Lax }); - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapControllers(); - endpoints.MapHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - }); - } - - private void RegisterAppInsights(IServiceCollection services) - { - services.AddApplicationInsightsTelemetry(Configuration); - services.AddApplicationInsightsKubernetesEnricher(); - } - } -} diff --git a/src/Services/Identity/Identity.API/Views/Account/AccessDenied.cshtml b/src/Services/Identity/Identity.API/Views/Account/AccessDenied.cshtml new file mode 100644 index 000000000..32e6c531b --- /dev/null +++ b/src/Services/Identity/Identity.API/Views/Account/AccessDenied.cshtml @@ -0,0 +1,7 @@ + +
+
+

Access Denied

+

You do not have access to that resource.

+
+
\ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Views/Account/LoggedOut.cshtml b/src/Services/Identity/Identity.API/Views/Account/LoggedOut.cshtml index f843973f8..3cc190b0b 100644 --- a/src/Services/Identity/Identity.API/Views/Account/LoggedOut.cshtml +++ b/src/Services/Identity/Identity.API/Views/Account/LoggedOut.cshtml @@ -1,6 +1,11 @@ -@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.LoggedOutViewModel +@model LoggedOutViewModel -