From c64822b38f291982efb1aaa87d403e9cfee31614 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Mon, 3 Nov 2025 23:22:21 +0100 Subject: [PATCH 01/32] feat: add Etsy OAuth provider --- .../AspNet.Security.OAuth.Etsy.csproj | 18 +++ .../EtsyAuthenticationConstants.cs | 92 +++++++++++++++ .../EtsyAuthenticationDefaults.cs | 48 ++++++++ .../EtsyAuthenticationExtensions.cs | 77 +++++++++++++ .../EtsyAuthenticationHandler.cs | 108 ++++++++++++++++++ .../EtsyAuthenticationOptions.cs | 48 ++++++++ .../EtsyPostConfigureOptions.cs | 45 ++++++++ 7 files changed, 436 insertions(+) create mode 100644 src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj create mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs create mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs create mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs create mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs create mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs create mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs diff --git a/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj b/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj new file mode 100644 index 000000000..105c515ab --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj @@ -0,0 +1,18 @@ + + + + $(DefaultNetCoreTargetFramework) + + + + ASP.NET Core security middleware enabling Etsy authentication. + Sonja + aspnetcore;authentication;etsy;oauth;security + + + + + + + + \ No newline at end of file diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs new file mode 100644 index 000000000..cbec6b902 --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs @@ -0,0 +1,92 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +namespace AspNet.Security.OAuth.Etsy; + +/// +/// Contains constants specific to the . +/// +public static class EtsyAuthenticationConstants +{ + public static class Claims + { + public const string UserId = "urn:etsy:user_id"; + public const string ShopId = "urn:etsy:shop_id"; + public const string PrimaryEmail = "urn:etsy:primary_email"; + public const string FirstName = "urn:etsy:first_name"; + public const string LastName = "urn:etsy:last_name"; + public const string ImageUrl = "urn:etsy:image_url"; + } + + public static class Scopes + { + /// Read user profile and email address + public const string EmailRead = "email_r"; + + /// Read user's listings + public const string ListingsRead = "listings_r"; + + /// Create and edit listings + public const string ListingsWrite = "listings_w"; + + /// Delete listings + public const string ListingsDelete = "listings_d"; + + /// Read shop information + public const string ShopsRead = "shops_r"; + + /// Update shop information + public const string ShopsWrite = "shops_w"; + + /// Delete shop information + public const string ShopsDelete = "shops_d"; + + /// Read transaction data + public const string TransactionsRead = "transactions_r"; + + /// Update transaction data + public const string TransactionsWrite = "transactions_w"; + + /// Read billing information + public const string BillingRead = "billing_r"; + + /// Read private profile information + public const string ProfileRead = "profile_r"; + + /// Update profile information + public const string ProfileWrite = "profile_w"; + + /// Read user's addresses + public const string AddressRead = "address_r"; + + /// Write user's addresses + public const string AddressWrite = "address_w"; + + /// Read user's favorites + public const string FavoritesRead = "favorites_r"; + + /// Write user's favorites + public const string FavoritesWrite = "favorites_w"; + + /// Read user's feedback + public const string FeedbackRead = "feedback_r"; + + /// Read user's shops + public const string ShopsMyRead = "shops_my_r"; + + /// Read user's cart + public const string CartRead = "cart_r"; + + /// Write user's cart + public const string CartWrite = "cart_w"; + + /// Read user's recommendations + public const string RecommendRead = "recommend_r"; + + /// Write user's recommendations + public const string RecommendWrite = "recommend_w"; + } +} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs new file mode 100644 index 000000000..ee43c6a8e --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs @@ -0,0 +1,48 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +namespace AspNet.Security.OAuth.Etsy; + +/// +/// Default values used by the Etsy authentication middleware. +/// +public static class EtsyAuthenticationDefaults +{ + /// + /// Default value for . + /// + public const string AuthenticationScheme = "Etsy"; + + /// + /// Default value for . + /// + public static readonly string DisplayName = "Etsy"; + + /// + /// Default value for . + /// + public static readonly string Issuer = "Etsy"; + + /// + /// Default value for . + /// + public static readonly string CallbackPath = "/signin-etsy"; + + /// + /// Default value for . + /// + public static readonly string AuthorizationEndpoint = "https://www.etsy.com/oauth/connect"; + + /// + /// Default value for . + /// + public static readonly string TokenEndpoint = "https://api.etsy.com/v3/public/oauth/token"; + + /// + /// Default value for . + /// + public static readonly string UserInformationEndpoint = "https://openapi.etsy.com/v3/application/users/me"; +} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs new file mode 100644 index 000000000..a515cf39c --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs @@ -0,0 +1,77 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using AspNet.Security.OAuth.Etsy; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods to add Etsy authentication capabilities to an HTTP application pipeline. +/// +public static class EtsyAuthenticationExtensions +{ + /// + /// Adds to the specified + /// , which enables Etsy authentication capabilities. + /// + /// The authentication builder. + /// The . + public static AuthenticationBuilder AddEtsy([NotNull] this AuthenticationBuilder builder) + { + return builder.AddEtsy(EtsyAuthenticationDefaults.AuthenticationScheme, options => { }); + } + + /// + /// Adds to the specified + /// , which enables Etsy authentication capabilities. + /// + /// The authentication builder. + /// The delegate used to configure the Etsy options. + /// The . + public static AuthenticationBuilder AddEtsy( + [NotNull] this AuthenticationBuilder builder, + [NotNull] Action configuration) + { + return builder.AddEtsy(EtsyAuthenticationDefaults.AuthenticationScheme, configuration); + } + + /// + /// Adds to the specified + /// , which enables Etsy authentication capabilities. + /// + /// The authentication builder. + /// The authentication scheme associated with this instance. + /// The delegate used to configure the Etsy options. + /// The . + public static AuthenticationBuilder AddEtsy( + [NotNull] this AuthenticationBuilder builder, + [NotNull] string scheme, + [NotNull] Action configuration) + { + return builder.AddEtsy(scheme, EtsyAuthenticationDefaults.DisplayName, configuration); + } + + /// + /// Adds to the specified + /// , which enables Etsy authentication capabilities. + /// + /// The authentication builder. + /// The authentication scheme associated with this instance. + /// The optional display name associated with this instance. + /// The delegate used to configure the Etsy options. + /// The . + public static AuthenticationBuilder AddEtsy( + [NotNull] this AuthenticationBuilder builder, + [NotNull] string scheme, + [CanBeNull] string caption, + [NotNull] Action configuration) + { + builder.Services.TryAddSingleton, EtsyPostConfigureOptions>(); + return builder.AddOAuth(scheme, caption, configuration); + } +} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs new file mode 100644 index 000000000..5473a9673 --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs @@ -0,0 +1,108 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using System.Globalization; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Text.Json; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace AspNet.Security.OAuth.Etsy; + +public partial class EtsyAuthenticationHandler : OAuthHandler +{ + public EtsyAuthenticationHandler( + [NotNull] IOptionsMonitor options, + [NotNull] ILoggerFactory logger, + [NotNull] UrlEncoder encoder) + : base(options, logger, encoder) + { + } + + protected override async Task CreateTicketAsync( + [NotNull] ClaimsIdentity identity, + [NotNull] AuthenticationProperties properties, + [NotNull] OAuthTokenResponse tokens) + { + // First, get the basic user info and shop_id from /v3/application/users/me + using var meRequest = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint); + meRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + meRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); + meRequest.Headers.Add("x-api-key", Options.ClientId); + + using var meResponse = await Backchannel.SendAsync(meRequest, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); + if (!meResponse.IsSuccessStatusCode) + { + await Log.UserProfileErrorAsync(Logger, meResponse, Context.RequestAborted); + throw new HttpRequestException("An error occurred while retrieving basic user info from Etsy."); + } + + using var mePayload = JsonDocument.Parse(await meResponse.Content.ReadAsStringAsync(Context.RequestAborted)); + var meRoot = mePayload.RootElement; + + // Extract user_id and shop_id from the /me response + // Both fields should always be present in a successful Etsy OAuth response + var userId = meRoot.GetProperty("user_id").GetInt64(); + var shopId = meRoot.GetProperty("shop_id").GetInt64(); + + // Add the basic claims from the /me endpoint + // Use shop_id as the primary identifier for Etsy (required for most API operations) + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, shopId.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(EtsyAuthenticationConstants.Claims.UserId, userId.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(EtsyAuthenticationConstants.Claims.ShopId, shopId.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.String, Options.ClaimsIssuer)); + + // Now get additional user details from /v3/application/users/{user_id} + var userDetailEndpoint = $"https://openapi.etsy.com/v3/application/users/{userId}"; + using var userRequest = new HttpRequestMessage(HttpMethod.Get, userDetailEndpoint); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); + userRequest.Headers.Add("x-api-key", Options.ClientId); + + using var userResponse = await Backchannel.SendAsync(userRequest, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); + if (userResponse.IsSuccessStatusCode) + { + using var userPayload = JsonDocument.Parse(await userResponse.Content.ReadAsStringAsync(Context.RequestAborted)); + + // Create context with the detailed user data for claim mapping + var principal = new ClaimsPrincipal(identity); + var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, userPayload.RootElement); + context.RunClaimActions(); + + await Events.CreatingTicket(context); + return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name); + } + else + { + // If detailed user info call fails, just create ticket with basic info + var principal = new ClaimsPrincipal(identity); + var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, meRoot); + + await Events.CreatingTicket(context); + return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name); + } + } + + private static partial class Log + { + internal static async Task UserProfileErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken) + { + UserProfileError( + logger, + response.StatusCode, + response.Headers.ToString(), + await response.Content.ReadAsStringAsync(cancellationToken)); + } + + [LoggerMessage(1, LogLevel.Error, "An error occurred while retrieving the user profile from Etsy: the remote server returned a {Status} response with the following payload: {Headers} {Body}.")] + private static partial void UserProfileError( + ILogger logger, + System.Net.HttpStatusCode status, + string headers, + string body); + } +} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs new file mode 100644 index 000000000..6e3f075a3 --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -0,0 +1,48 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using System.Security.Claims; +using static AspNet.Security.OAuth.Etsy.EtsyAuthenticationConstants; + +namespace AspNet.Security.OAuth.Etsy; + +/// +/// Defines a set of options used by . +/// +public class EtsyAuthenticationOptions : OAuthOptions +{ + public EtsyAuthenticationOptions() + { + ClaimsIssuer = EtsyAuthenticationDefaults.Issuer; + CallbackPath = EtsyAuthenticationDefaults.CallbackPath; + + AuthorizationEndpoint = EtsyAuthenticationDefaults.AuthorizationEndpoint; + TokenEndpoint = EtsyAuthenticationDefaults.TokenEndpoint; + UserInformationEndpoint = EtsyAuthenticationDefaults.UserInformationEndpoint; + + // Enable PKCE by default (required by Etsy) + UsePkce = true; + + // Enable refresh token support + SaveTokens = true; + + // Default scopes - Etsy requires at least one scope + Scope.Add(Scopes.EmailRead); + Scope.Add(Scopes.ShopsRead); + + // Map Etsy user fields to standard and custom claims + // These mappings apply to the /v3/application/users/{user_id} endpoint response + // Note: ClaimTypes.NameIdentifier, UserId, and ShopId are set programmatically + // in the handler from the /v3/application/users/me endpoint + ClaimActions.MapJsonKey(ClaimTypes.Email, "primary_email"); + ClaimActions.MapJsonKey(ClaimTypes.GivenName, "first_name"); + ClaimActions.MapJsonKey(ClaimTypes.Surname, "last_name"); + ClaimActions.MapJsonKey(Claims.PrimaryEmail, "primary_email"); + ClaimActions.MapJsonKey(Claims.FirstName, "first_name"); + ClaimActions.MapJsonKey(Claims.LastName, "last_name"); + ClaimActions.MapJsonKey(Claims.ImageUrl, "image_url_75x75"); + } +} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs new file mode 100644 index 000000000..bb19761de --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs @@ -0,0 +1,45 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using Microsoft.Extensions.Options; + +namespace AspNet.Security.OAuth.Etsy; + +/// +/// Contains the methods required to ensure that the Etsy configuration is valid. +/// +public class EtsyPostConfigureOptions : IPostConfigureOptions +{ + /// + /// Invoked to post-configure a TOptions instance. + /// + /// The name of the options instance being configured. + /// The options instance to configure. + public void PostConfigure(string? name, EtsyAuthenticationOptions options) + { + if (string.IsNullOrEmpty(options.ClientId)) + { + throw new ArgumentException("The Etsy Client ID cannot be null or empty.", nameof(options)); + } + + // Note: Client Secret validation removed - Etsy uses mandatory PKCE which provides + // cryptographic proof of authorization code ownership, potentially eliminating the + // need for client_secret in the token exchange. The ClientId (keystring) is used + // in the x-api-key header for API authentication. + + // Ensure PKCE is enabled (required by Etsy) + if (!options.UsePkce) + { + throw new ArgumentException("PKCE is required by Etsy and cannot be disabled.", nameof(options)); + } + + // Ensure at least one scope is requested (required by Etsy) + if (options.Scope.Count == 0) + { + throw new ArgumentException("At least one scope must be specified for Etsy authentication.", nameof(options)); + } + } +} From 1c257bbf5cc18e2e5097aef2eab9e4a000195dfc Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Mon, 3 Nov 2025 23:22:26 +0100 Subject: [PATCH 02/32] feat: add documentation for Etsy OAuth provider --- docs/etsy.md | 690 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 690 insertions(+) create mode 100644 docs/etsy.md diff --git a/docs/etsy.md b/docs/etsy.md new file mode 100644 index 000000000..d8dd50b82 --- /dev/null +++ b/docs/etsy.md @@ -0,0 +1,690 @@ +# Integrating the Etsy Provider + +## Example + +```csharp +services.AddAuthentication(options => /* Auth configuration */) + .AddEtsy(options => + { + options.ClientId = "my-etsy-api-key"; + // Note: ClientSecret is optional. PKCE is used for security. + }); +``` + +## Required Settings + +| Property Name | Property Type | Description | Default Value | +|:--|:--|:--|:--| +| `ClientId` | `string` | Your Etsy App API Key keystring from [Your Apps](https://www.etsy.com/developers/your-apps) | | + +## Optional Settings + +| Property Name | Property Type | Description | Default Value | +|:--|:--|:--|:--| +| `Scope` | `ICollection` | A collection of requested scopes. See [Etsy Scopes](https://developers.etsy.com/documentation/essentials/authentication#scopes) for available scopes. | `["email_r", "shops_r"]` | +| `SaveTokens` | `bool` | Defines whether access and refresh tokens should be stored in the authentication cookie | `true` | + +## Getting Started + +To use the Etsy provider, you need to: + +1. **Create an Etsy App** at [etsy.com/developers/your-apps](https://www.etsy.com/developers/your-apps) +2. **Configure your redirect URI** to match your callback path (default: `/signin-etsy`) +3. **Note your API Key and Secret** from the app settings + +## Important Notes + +- **PKCE Required**: Etsy requires PKCE (Proof Key for Code Exchange) for all OAuth flows. This is automatically enabled. +- **Client Secret Optional**: PKCE provides security without requiring a client secret. The provider works with only a Client ID. +- **Scopes Required**: Etsy requires at least one scope to be specified. The default scopes are `email_r` and `shops_r`. +- **Refresh Tokens**: Etsy provides refresh tokens with a 90-day lifetime, while access tokens expire after 1 hour. +- **User ID and Shop ID**: Etsy provides both a `user_id` and `shop_id`. The `shop_id` is used as the primary identifier (ClaimTypes.NameIdentifier) as it's required for most Etsy API operations. +- **Two-Step User Info**: The provider makes two API calls: + 1. `/v3/application/users/me` - Returns `user_id` and `shop_id` + 2. `/v3/application/users/{user_id}` - Returns detailed user profile information + +## Available Scopes + +Some commonly used Etsy scopes include: + +- `email_r` - Read user profile and email address +- `listings_r` - Read user's listings +- `listings_w` - Create and edit listings +- `shops_r` - Read shop information +- `shops_w` - Update shop information +- `transactions_r` - Read transaction data +- `transactions_w` - Update transaction data +- `profile_r` - Read private profile information +- `profile_w` - Update profile information + +For a complete list, see the [Etsy OAuth Scopes documentation](https://developers.etsy.com/documentation/essentials/authentication#scopes). + +## Claims + +The Etsy provider maps the following user profile fields to claims: + +| Claim Type | Value Source | Description | +|:--|:--|:--| +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | `shop_id` | The user's shop ID (primary identifier for Etsy API operations) | +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | `primary_email` | The user's primary email address | +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | `first_name` | The user's first name | +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | `last_name` | The user's last name | +| `urn:etsy:user_id` | `user_id` | The user's unique ID (Etsy-specific claim) | +| `urn:etsy:shop_id` | `shop_id` | The user's shop ID (Etsy-specific claim) | +| `urn:etsy:primary_email` | `primary_email` | The user's primary email (Etsy-specific claim) | +| `urn:etsy:first_name` | `first_name` | The user's first name (Etsy-specific claim) | +| `urn:etsy:last_name` | `last_name` | The user's last name (Etsy-specific claim) | +| `urn:etsy:image_url` | `image_url_75x75` | URL to the user's 75x75 profile image | + +## Usage Example + +### Configure Authentication in Program.cs + +# [Controller-based](#tab/controller/setup) + +```csharp +using AspNet.Security.OAuth.Etsy; +using Microsoft.AspNetCore.Authentication.Cookies; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme; +}) +.AddCookie() +.AddEtsy(options => +{ + options.ClientId = builder.Configuration["Etsy:ClientId"]!; + + // Optional: Add additional scopes + options.Scope.Add("listings_r"); + options.Scope.Add("transactions_r"); + + // SaveTokens is true by default, allowing you to retrieve + // access_token and refresh_token later + options.SaveTokens = true; +}); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); +``` + +# [Minimal API](#tab/minimal/setup) + +```csharp +using AspNet.Security.OAuth.Etsy; +using Microsoft.AspNetCore.Authentication.Cookies; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme; +}) +.AddCookie() +.AddEtsy(options => +{ + options.ClientId = builder.Configuration["Etsy:ClientId"]!; + + // Optional: Add additional scopes + options.Scope.Add("listings_r"); + options.Scope.Add("transactions_r"); + + // SaveTokens is true by default, allowing you to retrieve + // access_token and refresh_token later + options.SaveTokens = true; +}); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); + +// Map your minimal API endpoints +app.MapGet("/", () => "Hello World!"); + +// See individual endpoint examples below, or use the feature-based +// approach with app.MapEtsyAuth() shown at the bottom of this page + +app.Run(); +``` + +--- + +### Configuration with appsettings.json + +**How Configuration Works:** + +The `AddEtsy()` extension method accepts an `Action` delegate where you configure the provider. You can read values from configuration using `builder.Configuration["key"]` or bind entire sections. + +**Using Scope Constants:** + +Instead of magic strings, use the provided `EtsyAuthenticationConstants.Scopes` constants: + +```csharp +using AspNet.Security.OAuth.Etsy; + +.AddEtsy(options => +{ + options.ClientId = builder.Configuration["Etsy:ClientId"]!; + + // Use constants instead of magic strings + options.Scope.Add(EtsyAuthenticationConstants.Scopes.ListingsRead); + options.Scope.Add(EtsyAuthenticationConstants.Scopes.TransactionsRead); +}); +``` + +**Available Scope Constants:** + +| Constant | Scope Value | Description | +|:--|:--|:--| +| `Scopes.EmailRead` | `email_r` | Read user profile and email address | +| `Scopes.ListingsRead` | `listings_r` | Read user's listings | +| `Scopes.ListingsWrite` | `listings_w` | Create and edit listings | +| `Scopes.ListingsDelete` | `listings_d` | Delete listings | +| `Scopes.ShopsRead` | `shops_r` | Read shop information | +| `Scopes.ShopsWrite` | `shops_w` | Update shop information | +| `Scopes.ShopsDelete` | `shops_d` | Delete shop information | +| `Scopes.TransactionsRead` | `transactions_r` | Read transaction data | +| `Scopes.TransactionsWrite` | `transactions_w` | Update transaction data | +| `Scopes.BillingRead` | `billing_r` | Read billing information | +| `Scopes.ProfileRead` | `profile_r` | Read private profile information | +| `Scopes.ProfileWrite` | `profile_w` | Update profile information | +| `Scopes.AddressRead` | `address_r` | Read user's addresses | +| `Scopes.AddressWrite` | `address_w` | Write user's addresses | +| `Scopes.FavoritesRead` | `favorites_r` | Read user's favorites | +| `Scopes.FavoritesWrite` | `favorites_w` | Write user's favorites | +| `Scopes.FeedbackRead` | `feedback_r` | Read user's feedback | +| `Scopes.ShopsMyRead` | `shops_my_r` | Read user's shops | +| `Scopes.CartRead` | `cart_r` | Read user's cart | +| `Scopes.CartWrite` | `cart_w` | Write user's cart | +| `Scopes.RecommendRead` | `recommend_r` | Read user's recommendations | +| `Scopes.RecommendWrite` | `recommend_w` | Write user's recommendations | + +**Configuration Files:** + +The examples above use `builder.Configuration["Etsy:ClientId"]` to read from configuration. Here's how to set up your `appsettings.json`: + +**appsettings.json:** + +```json +{ + "Etsy": { + "ClientId": "your-etsy-api-key-here" + }, + "Logging": { + "LogLevel": { + "Default": "Information" + } + } +} +``` + +**appsettings.Development.json** (for local development): + +```json +{ + "Etsy": { + "ClientId": "your-development-api-key" + } +} +``` + +**Using Environment Variables** (recommended for production): + +```bash +# Linux/macOS +export Etsy__ClientId="your-production-api-key" + +# Windows PowerShell +$env:Etsy__ClientId = "your-production-api-key" + +# Docker +-e Etsy__ClientId="your-production-api-key" +``` + +**Using User Secrets** (for local development): + +```bash +dotnet user-secrets init +dotnet user-secrets set "Etsy:ClientId" "your-api-key" +``` + +**Advanced Configuration** (binding to options object): + +```csharp +// Create a configuration class +public class EtsySettings +{ + public string ClientId { get; set; } = string.Empty; + public List Scopes { get; set; } = new(); +} + +// In Program.cs +builder.Services.Configure( + builder.Configuration.GetSection("Etsy")); + +// Use it in authentication setup +builder.Services.AddEtsy(options => +{ + var etsySettings = builder.Configuration + .GetSection("Etsy") + .Get()!; + + options.ClientId = etsySettings.ClientId; + + foreach (var scope in etsySettings.Scopes) + { + options.Scope.Add(scope); + } +}); +``` + +**appsettings.json for advanced configuration:** + +```json +{ + "Etsy": { + "ClientId": "your-etsy-api-key", + "Scopes": [ + "email_r", + "shops_r", + "listings_r", + "transactions_r" + ] + } +} +``` + +### Accessing Claims + +# [Controller-based](#tab/controller/claims) + +```csharp +using AspNet.Security.OAuth.Etsy; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +[Authorize] +public class ProfileController : Controller +{ + public IActionResult Index() + { + // Get shop_id (primary identifier for Etsy API operations) + var shopId = User.FindFirstValue(ClaimTypes.NameIdentifier); + // OR + var shopIdAlt = User.FindFirstValue(EtsyAuthenticationConstants.Claims.ShopId); + + // Get user_id + var userId = User.FindFirstValue(EtsyAuthenticationConstants.Claims.UserId); + + // Get other user information + var email = User.FindFirstValue(ClaimTypes.Email); + var firstName = User.FindFirstValue(ClaimTypes.GivenName); + var lastName = User.FindFirstValue(ClaimTypes.Surname); + var imageUrl = User.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl); + + var model = new + { + ShopId = shopId, + UserId = userId, + Email = email, + FirstName = firstName, + LastName = lastName, + ImageUrl = imageUrl + }; + + return View(model); + } +} +``` + +# [Minimal API](#tab/minimal/claims) + +```csharp +using AspNet.Security.OAuth.Etsy; +using System.Security.Claims; + +app.MapGet("/profile", (ClaimsPrincipal user) => +{ + // Get shop_id (primary identifier for Etsy API operations) + var shopId = user.FindFirstValue(ClaimTypes.NameIdentifier); + // OR + var shopIdAlt = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ShopId); + + // Get user_id + var userId = user.FindFirstValue(EtsyAuthenticationConstants.Claims.UserId); + + // Get other user information + var email = user.FindFirstValue(ClaimTypes.Email); + var firstName = user.FindFirstValue(ClaimTypes.GivenName); + var lastName = user.FindFirstValue(ClaimTypes.Surname); + var imageUrl = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl); + + return Results.Ok(new + { + ShopId = shopId, + UserId = userId, + Email = email, + FirstName = firstName, + LastName = lastName, + ImageUrl = imageUrl + }); +}).RequireAuthorization(); +``` + +--- + +### Retrieving Access and Refresh Tokens + +# [Controller-based](#tab/controller/tokens) + +```csharp +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +[Authorize] +public class TokenController : Controller +{ + public async Task GetTokens() + { + // Retrieve the stored tokens + var accessToken = await HttpContext.GetTokenAsync("access_token"); + var refreshToken = await HttpContext.GetTokenAsync("refresh_token"); + var expiresAt = await HttpContext.GetTokenAsync("expires_at"); + + return Ok(new + { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }); + } +} +``` + +# [Minimal API](#tab/minimal/tokens) + +```csharp +using Microsoft.AspNetCore.Authentication; + +app.MapGet("/tokens", async (HttpContext context) => +{ + // Retrieve the stored tokens + var accessToken = await context.GetTokenAsync("access_token"); + var refreshToken = await context.GetTokenAsync("refresh_token"); + var expiresAt = await context.GetTokenAsync("expires_at"); + + return Results.Ok(new + { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }); +}).RequireAuthorization(); +``` + +--- + +### Triggering Authentication + +# [Controller-based](#tab/controller/auth) + +```csharp +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using AspNet.Security.OAuth.Etsy; + +public class AccountController : Controller +{ + [HttpGet("~/signin")] + public IActionResult SignIn(string returnUrl = "/") + { + return Challenge(new AuthenticationProperties + { + RedirectUri = returnUrl + }, EtsyAuthenticationDefaults.AuthenticationScheme); + } + + [HttpGet("~/signout")] + public async Task SignOut() + { + await HttpContext.SignOutAsync(); + return RedirectToAction("Index", "Home"); + } +} +``` + +# [Minimal API](#tab/minimal/auth) + +```csharp +using Microsoft.AspNetCore.Authentication; +using AspNet.Security.OAuth.Etsy; + +app.MapGet("/signin", (string? returnUrl) => +{ + return Results.Challenge( + new AuthenticationProperties + { + RedirectUri = returnUrl ?? "/" + }, + new[] { EtsyAuthenticationDefaults.AuthenticationScheme }); +}); + +app.MapGet("/signout", async (HttpContext context) => +{ + await context.SignOutAsync(); + return Results.Redirect("/"); +}); +``` + +--- + +### Complete Minimal API Example with Feature-Based Structure + +This example demonstrates a modern, feature-based approach using extension methods and `MapGroup`. + +**How Authentication Connects:** + +1. **`AddAuthentication().AddEtsy()`** registers the Etsy OAuth handler with scheme `"Etsy"` +2. **`app.UseAuthentication()`** activates the authentication middleware +3. **`TypedResults.Challenge(..., authenticationSchemes)`** triggers the registered Etsy handler +4. **`.RequireAuthorization()`** uses the authenticated user from the handler +5. **`ClaimsPrincipal`** is automatically injected with claims from successful authentication + +**Program.cs:** + +```csharp +using AspNet.Security.OAuth.Etsy; +using Microsoft.AspNetCore.Authentication.Cookies; +using MyApi.Features.Authorization; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme; +}) +.AddCookie() +.AddEtsy(options => +{ + options.ClientId = builder.Configuration["Etsy:ClientId"]!; + options.Scope.Add(EtsyAuthenticationConstants.Scopes.ListingsRead); + options.Scope.Add(EtsyAuthenticationConstants.Scopes.TransactionsRead); +}); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); + +// Map Etsy authentication endpoints +app.MapEtsyAuth(); + +app.Run(); +``` + +**Features/Authorization/EtsyAuthEndpoints.cs:** + +```csharp +using AspNet.Security.OAuth.Etsy; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http.HttpResults; +using System.Security.Claims; + +namespace MyApi.Features.Authorization; + +public static class EtsyAuthEndpoints +{ + public static IEndpointRouteBuilder MapEtsyAuth(this IEndpointRouteBuilder app) + { + var group = app.MapGroup("/etsy") + .WithTags("Etsy Authentication"); + + // Authentication endpoints + // When user navigates here, Challenge triggers the Etsy OAuth handler + group.MapGet("/signin", SignInAsync) + .WithName("EtsySignIn") + .WithSummary("Initiate Etsy OAuth authentication"); + + group.MapGet("/signout", SignOutAsync) + .WithName("EtsySignOut") + .WithSummary("Sign out from Etsy authentication"); + + // Protected endpoints requiring authorization + // .RequireAuthorization() checks if user is authenticated + // ClaimsPrincipal is automatically injected with claims from Etsy handler + group.MapGet("/profile", GetProfileAsync) + .RequireAuthorization() + .WithName("EtsyProfile") + .WithSummary("Get authenticated user's Etsy profile"); + + group.MapGet("/tokens", GetTokensAsync) + .RequireAuthorization() + .WithName("EtsyTokens") + .WithSummary("Get OAuth access and refresh tokens"); + + return app; + } + + private static IResult SignInAsync(string? returnUrl) + { + // This triggers the EtsyAuthenticationHandler (registered via AddEtsy) + // The scheme name "Etsy" connects to the registered handler + return TypedResults.Challenge( + new AuthenticationProperties { RedirectUri = returnUrl ?? "/" }, + new[] { EtsyAuthenticationDefaults.AuthenticationScheme }); + } + + private static async Task SignOutAsync(HttpContext context) + { + // Signs out from the cookie authentication (removes the session) + await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + return TypedResults.Redirect("/"); + } + + private static Task> GetProfileAsync(ClaimsPrincipal user) + { + // The ClaimsPrincipal 'user' parameter is automatically injected + // It contains claims populated by EtsyAuthenticationHandler + // from the Etsy API responses + var profile = new UserProfile + { + ShopId = user.FindFirstValue(ClaimTypes.NameIdentifier)!, + UserId = user.FindFirstValue(EtsyAuthenticationConstants.Claims.UserId)!, + Email = user.FindFirstValue(ClaimTypes.Email), + FirstName = user.FindFirstValue(ClaimTypes.GivenName), + LastName = user.FindFirstValue(ClaimTypes.Surname), + ImageUrl = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl) + }; + + return Task.FromResult(TypedResults.Ok(profile)); + } + + private static async Task> GetTokensAsync(HttpContext context) + { + // Tokens were saved by SaveTokens = true in AddEtsy options + // Retrieved from the authentication properties stored in the cookie + var tokenInfo = new TokenInfo + { + AccessToken = await context.GetTokenAsync("access_token"), + RefreshToken = await context.GetTokenAsync("refresh_token"), + ExpiresAt = await context.GetTokenAsync("expires_at") + }; + + return TypedResults.Ok(tokenInfo); + } +} +``` + +**Features/Authorization/Models/UserProfile.cs:** + +```csharp +namespace MyApi.Features.Authorization; + +public record UserProfile +{ + public required string ShopId { get; init; } + public required string UserId { get; init; } + public string? Email { get; init; } + public string? FirstName { get; init; } + public string? LastName { get; init; } + public string? ImageUrl { get; init; } +} +``` + +**Features/Authorization/Models/TokenInfo.cs:** + +```csharp +namespace MyApi.Features.Authorization; + +public record TokenInfo +{ + public string? AccessToken { get; init; } + public string? RefreshToken { get; init; } + public string? ExpiresAt { get; init; } +} +``` + +**Project Structure:** + +```text +MyApi/ +├── Program.cs +├── Features/ +│ └── Authorization/ +│ ├── EtsyAuthEndpoints.cs +│ └── Models/ +│ ├── UserProfile.cs +│ └── TokenInfo.cs +``` + +**Usage:** + +- Navigate to `/etsy/signin` to authenticate with Etsy +- After authentication, access `/etsy/profile` to see user information +- Access `/etsy/tokens` to retrieve OAuth tokens +- Navigate to `/etsy/signout` to sign out + +**Benefits of this approach:** + +- **Feature-based organization**: All Etsy authentication logic is contained in one feature folder +- **Clean Program.cs**: Single line `app.MapEtsyAuth()` keeps startup code minimal +- **Testability**: Extension method makes it easy to test endpoint registration +- **Discoverability**: Endpoints are documented with `WithName`, `WithSummary`, and `WithTags` +- **Extensibility**: Easy to add more Etsy-related endpoints to the same group +- **Type safety**: Uses `TypedResults` for strongly-typed responses From 47426929ce54018aec078a29d40483d9d4dc3b75 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Mon, 3 Nov 2025 23:22:36 +0100 Subject: [PATCH 03/32] test: add tests for Etsy OAuth provider --- .../Etsy/EtsyTests.cs | 42 +++++++++++++++++++ .../Etsy/bundle.json | 39 +++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs create mode 100644 test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs new file mode 100644 index 000000000..8243a8866 --- /dev/null +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs @@ -0,0 +1,42 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using System.Security.Claims; +using AspNet.Security.OAuth.Etsy; +using Microsoft.AspNetCore.Authentication; +using Xunit; +using Xunit.Abstractions; + +namespace AspNet.Security.OAuth.Providers.Tests.Etsy; + +public class EtsyTests : OAuthTests +{ + public EtsyTests(ITestOutputHelper outputHelper) + : base(outputHelper) + { + } + + public override string DefaultScheme => EtsyAuthenticationDefaults.AuthenticationScheme; + + protected internal override void RegisterAuthentication(AuthenticationBuilder builder) + { + builder.AddEtsy(options => ConfigureDefaults(builder, options)); + } + + [Theory] + [InlineData(ClaimTypes.NameIdentifier, "789012")] // shop_id and user_id is used as primary identifier! + [InlineData(ClaimTypes.Email, "test@example.com")] + [InlineData(ClaimTypes.GivenName, "Test")] + [InlineData(ClaimTypes.Surname, "User")] + [InlineData("urn:etsy:user_id", "123456")] + [InlineData("urn:etsy:shop_id", "789012")] + [InlineData("urn:etsy:primary_email", "test@example.com")] + [InlineData("urn:etsy:first_name", "Test")] + [InlineData("urn:etsy:last_name", "User")] + [InlineData("urn:etsy:image_url", "https://i.etsystatic.com/test/test_75x75.jpg")] + public async Task Can_Sign_In_Using_Etsy(string claimType, string claimValue) + => await AuthenticateUserAndAssertClaimValue(claimType, claimValue); +} diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json new file mode 100644 index 000000000..79c73d7b1 --- /dev/null +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://raw.githubusercontent.com/justeat/httpclient-interception/master/src/HttpClientInterception/Bundles/http-request-bundle-schema.json", + "items": [ + { + "comment": "Etsy OAuth 2.0 token exchange endpoint", + "uri": "https://api.etsy.com/v3/public/oauth/token", + "method": "POST", + "contentFormat": "json", + "contentJson": { + "access_token": "secret-access-token", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "secret-refresh-token", + "scope": "email_r shops_r" + } + }, + { + "comment": "Etsy /v3/application/users/me endpoint - returns basic user and shop IDs", + "uri": "https://openapi.etsy.com/v3/application/users/me", + "contentFormat": "json", + "contentJson": { + "user_id": 123456, + "shop_id": 789012 + } + }, + { + "comment": "Etsy /v3/application/users/{user_id} endpoint - returns detailed user information", + "uri": "https://openapi.etsy.com/v3/application/users/123456", + "contentFormat": "json", + "contentJson": { + "user_id": 123456, + "primary_email": "test@example.com", + "first_name": "Test", + "last_name": "User", + "image_url_75x75": "https://i.etsystatic.com/test/test_75x75.jpg" + } + } + ] +} \ No newline at end of file From 5681e48e9fe194966fc24cdccda6b1aae2185ab1 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Thu, 6 Nov 2025 20:35:11 +0100 Subject: [PATCH 04/32] chore: Compare and align to other existing Providers - Added `EtsyAuthenticationDefaults` default value fields like `EtsyBaseUri` and `UserDetailsPath` - Added xml docs onto all `EtsyAuthenticationDefaults` fields - Added `EtsyAuthenticationOptions.IncludeDetailedUserInfo` Property - removed `EtsyPostConfigureOptions` as Etsy does not have dynamic endpoints or domain like GitHub - integrated validation logik into `EtsyAuthenticationOptions.Validate` override, like used for issue 610 fix --- AspNet.Security.OAuth.Providers.sln | 899 +++++++++++++++++- .../EtsyAuthenticationAccessType.cs | 18 + .../EtsyAuthenticationDefaults.cs | 23 +- .../EtsyAuthenticationExtensions.cs | 3 +- .../EtsyAuthenticationHandler.cs | 63 +- .../EtsyAuthenticationOptions.cs | 90 +- .../EtsyPostConfigureOptions.cs | 45 - 7 files changed, 1052 insertions(+), 89 deletions(-) create mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs delete mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs diff --git a/AspNet.Security.OAuth.Providers.sln b/AspNet.Security.OAuth.Providers.sln index 9dca8f93d..1d68261a7 100644 --- a/AspNet.Security.OAuth.Providers.sln +++ b/AspNet.Security.OAuth.Providers.sln @@ -187,6 +187,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C2CA4B38-A docs\docusign.md = docs\docusign.md docs\dropbox.md = docs\dropbox.md docs\ebay.md = docs\ebay.md + docs\etsy.md = docs\etsy.md docs\eveonline.md = docs\eveonline.md docs\foursquare.md = docs\foursquare.md docs\gitcode.md = docs\gitcode.md @@ -198,7 +199,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C2CA4B38-A docs\kook.md = docs\kook.md docs\lichess.md = docs\lichess.md docs\line.md = docs\line.md + docs\linear.md = docs\linear.md docs\linkedin.md = docs\linkedin.md + docs\miro.md = docs\miro.md docs\moodle.md = docs\moodle.md docs\odnoklassniki.md = docs\odnoklassniki.md docs\okta.md = docs\okta.md @@ -217,13 +220,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C2CA4B38-A docs\twitch.md = docs\twitch.md docs\twitter.md = docs\twitter.md docs\vkontakte.md = docs\vkontakte.md + docs\webflow.md = docs\webflow.md docs\weibo.md = docs\weibo.md docs\workweixin.md = docs\workweixin.md docs\xumm.md = docs\xumm.md docs\zendesk.md = docs\zendesk.md - docs\webflow.md = docs\webflow.md - docs\miro.md = docs\miro.md - docs\linear.md = docs\linear.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Basecamp", "src\AspNet.Security.OAuth.Basecamp\AspNet.Security.OAuth.Basecamp.csproj", "{42306484-B2BF-4B52-B950-E0CDFA58B02A}" @@ -328,450 +329,1341 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Linear", "src\AspNet.Security.OAuth.Linear\AspNet.Security.OAuth.Linear.csproj", "{B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Bilibili", "src\AspNet.Security.OAuth.Bilibili\AspNet.Security.OAuth.Bilibili.csproj", "{8350C405-9E17-4110-B9A8-0AB43A8816B7}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Contentful", "src\AspNet.Security.OAuth.Contentful\AspNet.Security.OAuth.Contentful.csproj", "{B1F6EA42-7B1B-469E-B304-6B2E6FE39852}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Etsy", "src\AspNet.Security.OAuth.Etsy\AspNet.Security.OAuth.Etsy.csproj", "{53B5B8F0-023E-4D2D-84F0-5B68610682A4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|x64.Build.0 = Debug|Any CPU + {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|x86.Build.0 = Debug|Any CPU {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|Any CPU.Build.0 = Release|Any CPU + {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|x64.ActiveCfg = Release|Any CPU + {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|x64.Build.0 = Release|Any CPU + {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|x86.ActiveCfg = Release|Any CPU + {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|x86.Build.0 = Release|Any CPU {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|x64.ActiveCfg = Debug|Any CPU + {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|x64.Build.0 = Debug|Any CPU + {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|x86.ActiveCfg = Debug|Any CPU + {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|x86.Build.0 = Debug|Any CPU {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|Any CPU.ActiveCfg = Release|Any CPU {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|Any CPU.Build.0 = Release|Any CPU + {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|x64.ActiveCfg = Release|Any CPU + {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|x64.Build.0 = Release|Any CPU + {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|x86.ActiveCfg = Release|Any CPU + {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|x86.Build.0 = Release|Any CPU {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|x64.ActiveCfg = Debug|Any CPU + {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|x64.Build.0 = Debug|Any CPU + {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|x86.ActiveCfg = Debug|Any CPU + {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|x86.Build.0 = Debug|Any CPU {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|Any CPU.ActiveCfg = Release|Any CPU {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|Any CPU.Build.0 = Release|Any CPU + {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|x64.ActiveCfg = Release|Any CPU + {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|x64.Build.0 = Release|Any CPU + {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|x86.ActiveCfg = Release|Any CPU + {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|x86.Build.0 = Release|Any CPU {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|x64.ActiveCfg = Debug|Any CPU + {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|x64.Build.0 = Debug|Any CPU + {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|x86.ActiveCfg = Debug|Any CPU + {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|x86.Build.0 = Debug|Any CPU {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|Any CPU.Build.0 = Release|Any CPU + {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|x64.ActiveCfg = Release|Any CPU + {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|x64.Build.0 = Release|Any CPU + {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|x86.ActiveCfg = Release|Any CPU + {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|x86.Build.0 = Release|Any CPU {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|x64.ActiveCfg = Debug|Any CPU + {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|x64.Build.0 = Debug|Any CPU + {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|x86.ActiveCfg = Debug|Any CPU + {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|x86.Build.0 = Debug|Any CPU {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|Any CPU.Build.0 = Release|Any CPU + {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|x64.ActiveCfg = Release|Any CPU + {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|x64.Build.0 = Release|Any CPU + {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|x86.ActiveCfg = Release|Any CPU + {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|x86.Build.0 = Release|Any CPU {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|x64.ActiveCfg = Debug|Any CPU + {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|x64.Build.0 = Debug|Any CPU + {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|x86.ActiveCfg = Debug|Any CPU + {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|x86.Build.0 = Debug|Any CPU {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|Any CPU.Build.0 = Release|Any CPU + {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|x64.ActiveCfg = Release|Any CPU + {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|x64.Build.0 = Release|Any CPU + {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|x86.ActiveCfg = Release|Any CPU + {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|x86.Build.0 = Release|Any CPU {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|x64.ActiveCfg = Debug|Any CPU + {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|x64.Build.0 = Debug|Any CPU + {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|x86.ActiveCfg = Debug|Any CPU + {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|x86.Build.0 = Debug|Any CPU {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|Any CPU.ActiveCfg = Release|Any CPU {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|Any CPU.Build.0 = Release|Any CPU + {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|x64.ActiveCfg = Release|Any CPU + {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|x64.Build.0 = Release|Any CPU + {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|x86.ActiveCfg = Release|Any CPU + {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|x86.Build.0 = Release|Any CPU {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|x64.ActiveCfg = Debug|Any CPU + {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|x64.Build.0 = Debug|Any CPU + {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|x86.ActiveCfg = Debug|Any CPU + {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|x86.Build.0 = Debug|Any CPU {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|Any CPU.Build.0 = Release|Any CPU + {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|x64.ActiveCfg = Release|Any CPU + {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|x64.Build.0 = Release|Any CPU + {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|x86.ActiveCfg = Release|Any CPU + {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|x86.Build.0 = Release|Any CPU {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|x64.ActiveCfg = Debug|Any CPU + {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|x64.Build.0 = Debug|Any CPU + {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|x86.ActiveCfg = Debug|Any CPU + {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|x86.Build.0 = Debug|Any CPU {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|Any CPU.ActiveCfg = Release|Any CPU {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|Any CPU.Build.0 = Release|Any CPU + {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|x64.ActiveCfg = Release|Any CPU + {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|x64.Build.0 = Release|Any CPU + {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|x86.ActiveCfg = Release|Any CPU + {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|x86.Build.0 = Release|Any CPU {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|x64.ActiveCfg = Debug|Any CPU + {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|x64.Build.0 = Debug|Any CPU + {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|x86.ActiveCfg = Debug|Any CPU + {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|x86.Build.0 = Debug|Any CPU {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|Any CPU.Build.0 = Release|Any CPU + {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|x64.ActiveCfg = Release|Any CPU + {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|x64.Build.0 = Release|Any CPU + {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|x86.ActiveCfg = Release|Any CPU + {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|x86.Build.0 = Release|Any CPU {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|x64.ActiveCfg = Debug|Any CPU + {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|x64.Build.0 = Debug|Any CPU + {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|x86.ActiveCfg = Debug|Any CPU + {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|x86.Build.0 = Debug|Any CPU {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|Any CPU.ActiveCfg = Release|Any CPU {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|Any CPU.Build.0 = Release|Any CPU + {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|x64.ActiveCfg = Release|Any CPU + {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|x64.Build.0 = Release|Any CPU + {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|x86.ActiveCfg = Release|Any CPU + {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|x86.Build.0 = Release|Any CPU {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|x64.ActiveCfg = Debug|Any CPU + {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|x64.Build.0 = Debug|Any CPU + {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|x86.ActiveCfg = Debug|Any CPU + {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|x86.Build.0 = Debug|Any CPU {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|Any CPU.Build.0 = Release|Any CPU + {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|x64.ActiveCfg = Release|Any CPU + {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|x64.Build.0 = Release|Any CPU + {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|x86.ActiveCfg = Release|Any CPU + {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|x86.Build.0 = Release|Any CPU {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|x64.ActiveCfg = Debug|Any CPU + {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|x64.Build.0 = Debug|Any CPU + {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|x86.ActiveCfg = Debug|Any CPU + {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|x86.Build.0 = Debug|Any CPU {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|Any CPU.Build.0 = Release|Any CPU + {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|x64.ActiveCfg = Release|Any CPU + {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|x64.Build.0 = Release|Any CPU + {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|x86.ActiveCfg = Release|Any CPU + {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|x86.Build.0 = Release|Any CPU {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|x64.ActiveCfg = Debug|Any CPU + {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|x64.Build.0 = Debug|Any CPU + {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|x86.ActiveCfg = Debug|Any CPU + {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|x86.Build.0 = Debug|Any CPU {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|Any CPU.Build.0 = Release|Any CPU + {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|x64.ActiveCfg = Release|Any CPU + {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|x64.Build.0 = Release|Any CPU + {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|x86.ActiveCfg = Release|Any CPU + {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|x86.Build.0 = Release|Any CPU {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|x64.ActiveCfg = Debug|Any CPU + {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|x64.Build.0 = Debug|Any CPU + {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|x86.ActiveCfg = Debug|Any CPU + {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|x86.Build.0 = Debug|Any CPU {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|Any CPU.Build.0 = Release|Any CPU + {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|x64.ActiveCfg = Release|Any CPU + {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|x64.Build.0 = Release|Any CPU + {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|x86.ActiveCfg = Release|Any CPU + {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|x86.Build.0 = Release|Any CPU {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|x64.Build.0 = Debug|Any CPU + {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|x86.ActiveCfg = Debug|Any CPU + {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|x86.Build.0 = Debug|Any CPU {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|Any CPU.ActiveCfg = Release|Any CPU {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|Any CPU.Build.0 = Release|Any CPU + {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|x64.ActiveCfg = Release|Any CPU + {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|x64.Build.0 = Release|Any CPU + {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|x86.ActiveCfg = Release|Any CPU + {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|x86.Build.0 = Release|Any CPU {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|x64.Build.0 = Debug|Any CPU + {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|x86.ActiveCfg = Debug|Any CPU + {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|x86.Build.0 = Debug|Any CPU {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|Any CPU.Build.0 = Release|Any CPU + {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|x64.ActiveCfg = Release|Any CPU + {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|x64.Build.0 = Release|Any CPU + {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|x86.ActiveCfg = Release|Any CPU + {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|x86.Build.0 = Release|Any CPU {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|x64.ActiveCfg = Debug|Any CPU + {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|x64.Build.0 = Debug|Any CPU + {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|x86.ActiveCfg = Debug|Any CPU + {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|x86.Build.0 = Debug|Any CPU {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|Any CPU.ActiveCfg = Release|Any CPU {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|Any CPU.Build.0 = Release|Any CPU + {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|x64.ActiveCfg = Release|Any CPU + {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|x64.Build.0 = Release|Any CPU + {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|x86.ActiveCfg = Release|Any CPU + {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|x86.Build.0 = Release|Any CPU {C20BE880-52CB-491C-977C-F08702376766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C20BE880-52CB-491C-977C-F08702376766}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C20BE880-52CB-491C-977C-F08702376766}.Debug|x64.ActiveCfg = Debug|Any CPU + {C20BE880-52CB-491C-977C-F08702376766}.Debug|x64.Build.0 = Debug|Any CPU + {C20BE880-52CB-491C-977C-F08702376766}.Debug|x86.ActiveCfg = Debug|Any CPU + {C20BE880-52CB-491C-977C-F08702376766}.Debug|x86.Build.0 = Debug|Any CPU {C20BE880-52CB-491C-977C-F08702376766}.Release|Any CPU.ActiveCfg = Release|Any CPU {C20BE880-52CB-491C-977C-F08702376766}.Release|Any CPU.Build.0 = Release|Any CPU + {C20BE880-52CB-491C-977C-F08702376766}.Release|x64.ActiveCfg = Release|Any CPU + {C20BE880-52CB-491C-977C-F08702376766}.Release|x64.Build.0 = Release|Any CPU + {C20BE880-52CB-491C-977C-F08702376766}.Release|x86.ActiveCfg = Release|Any CPU + {C20BE880-52CB-491C-977C-F08702376766}.Release|x86.Build.0 = Release|Any CPU {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|x64.ActiveCfg = Debug|Any CPU + {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|x64.Build.0 = Debug|Any CPU + {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|x86.ActiveCfg = Debug|Any CPU + {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|x86.Build.0 = Debug|Any CPU {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|Any CPU.Build.0 = Release|Any CPU + {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|x64.ActiveCfg = Release|Any CPU + {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|x64.Build.0 = Release|Any CPU + {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|x86.ActiveCfg = Release|Any CPU + {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|x86.Build.0 = Release|Any CPU {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|x64.ActiveCfg = Debug|Any CPU + {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|x64.Build.0 = Debug|Any CPU + {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|x86.ActiveCfg = Debug|Any CPU + {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|x86.Build.0 = Debug|Any CPU {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|Any CPU.Build.0 = Release|Any CPU + {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|x64.ActiveCfg = Release|Any CPU + {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|x64.Build.0 = Release|Any CPU + {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|x86.ActiveCfg = Release|Any CPU + {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|x86.Build.0 = Release|Any CPU {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|x64.ActiveCfg = Debug|Any CPU + {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|x64.Build.0 = Debug|Any CPU + {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|x86.ActiveCfg = Debug|Any CPU + {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|x86.Build.0 = Debug|Any CPU {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|Any CPU.Build.0 = Release|Any CPU + {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|x64.ActiveCfg = Release|Any CPU + {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|x64.Build.0 = Release|Any CPU + {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|x86.ActiveCfg = Release|Any CPU + {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|x86.Build.0 = Release|Any CPU {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|x64.ActiveCfg = Debug|Any CPU + {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|x64.Build.0 = Debug|Any CPU + {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|x86.ActiveCfg = Debug|Any CPU + {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|x86.Build.0 = Debug|Any CPU {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|Any CPU.Build.0 = Release|Any CPU + {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|x64.ActiveCfg = Release|Any CPU + {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|x64.Build.0 = Release|Any CPU + {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|x86.ActiveCfg = Release|Any CPU + {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|x86.Build.0 = Release|Any CPU {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|x64.ActiveCfg = Debug|Any CPU + {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|x64.Build.0 = Debug|Any CPU + {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|x86.ActiveCfg = Debug|Any CPU + {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|x86.Build.0 = Debug|Any CPU {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|Any CPU.Build.0 = Release|Any CPU + {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|x64.ActiveCfg = Release|Any CPU + {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|x64.Build.0 = Release|Any CPU + {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|x86.ActiveCfg = Release|Any CPU + {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|x86.Build.0 = Release|Any CPU {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|x64.ActiveCfg = Debug|Any CPU + {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|x64.Build.0 = Debug|Any CPU + {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|x86.ActiveCfg = Debug|Any CPU + {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|x86.Build.0 = Debug|Any CPU {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|Any CPU.ActiveCfg = Release|Any CPU {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|Any CPU.Build.0 = Release|Any CPU + {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|x64.ActiveCfg = Release|Any CPU + {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|x64.Build.0 = Release|Any CPU + {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|x86.ActiveCfg = Release|Any CPU + {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|x86.Build.0 = Release|Any CPU {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|x64.ActiveCfg = Debug|Any CPU + {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|x64.Build.0 = Debug|Any CPU + {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|x86.ActiveCfg = Debug|Any CPU + {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|x86.Build.0 = Debug|Any CPU {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|Any CPU.ActiveCfg = Release|Any CPU {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|Any CPU.Build.0 = Release|Any CPU + {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|x64.ActiveCfg = Release|Any CPU + {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|x64.Build.0 = Release|Any CPU + {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|x86.ActiveCfg = Release|Any CPU + {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|x86.Build.0 = Release|Any CPU {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|x64.ActiveCfg = Debug|Any CPU + {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|x64.Build.0 = Debug|Any CPU + {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|x86.ActiveCfg = Debug|Any CPU + {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|x86.Build.0 = Debug|Any CPU {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|Any CPU.Build.0 = Release|Any CPU + {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|x64.ActiveCfg = Release|Any CPU + {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|x64.Build.0 = Release|Any CPU + {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|x86.ActiveCfg = Release|Any CPU + {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|x86.Build.0 = Release|Any CPU {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|Any CPU.Build.0 = Debug|Any CPU + {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|x64.ActiveCfg = Debug|Any CPU + {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|x64.Build.0 = Debug|Any CPU + {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|x86.ActiveCfg = Debug|Any CPU + {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|x86.Build.0 = Debug|Any CPU {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|Any CPU.ActiveCfg = Release|Any CPU {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|Any CPU.Build.0 = Release|Any CPU + {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|x64.ActiveCfg = Release|Any CPU + {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|x64.Build.0 = Release|Any CPU + {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|x86.ActiveCfg = Release|Any CPU + {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|x86.Build.0 = Release|Any CPU {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|x64.ActiveCfg = Debug|Any CPU + {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|x64.Build.0 = Debug|Any CPU + {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|x86.ActiveCfg = Debug|Any CPU + {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|x86.Build.0 = Debug|Any CPU {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|Any CPU.Build.0 = Release|Any CPU + {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|x64.ActiveCfg = Release|Any CPU + {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|x64.Build.0 = Release|Any CPU + {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|x86.ActiveCfg = Release|Any CPU + {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|x86.Build.0 = Release|Any CPU {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|x64.ActiveCfg = Debug|Any CPU + {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|x64.Build.0 = Debug|Any CPU + {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|x86.Build.0 = Debug|Any CPU {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|Any CPU.Build.0 = Release|Any CPU + {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|x64.ActiveCfg = Release|Any CPU + {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|x64.Build.0 = Release|Any CPU + {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|x86.ActiveCfg = Release|Any CPU + {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|x86.Build.0 = Release|Any CPU {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|x64.Build.0 = Debug|Any CPU + {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|x86.Build.0 = Debug|Any CPU {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|Any CPU.Build.0 = Release|Any CPU + {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|x64.ActiveCfg = Release|Any CPU + {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|x64.Build.0 = Release|Any CPU + {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|x86.ActiveCfg = Release|Any CPU + {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|x86.Build.0 = Release|Any CPU {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|x64.Build.0 = Debug|Any CPU + {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|x86.Build.0 = Debug|Any CPU {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|Any CPU.Build.0 = Release|Any CPU + {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|x64.ActiveCfg = Release|Any CPU + {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|x64.Build.0 = Release|Any CPU + {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|x86.ActiveCfg = Release|Any CPU + {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|x86.Build.0 = Release|Any CPU {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|x64.ActiveCfg = Debug|Any CPU + {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|x64.Build.0 = Debug|Any CPU + {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|x86.ActiveCfg = Debug|Any CPU + {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|x86.Build.0 = Debug|Any CPU {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|Any CPU.Build.0 = Release|Any CPU + {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|x64.ActiveCfg = Release|Any CPU + {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|x64.Build.0 = Release|Any CPU + {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|x86.ActiveCfg = Release|Any CPU + {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|x86.Build.0 = Release|Any CPU {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|x64.ActiveCfg = Debug|Any CPU + {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|x64.Build.0 = Debug|Any CPU + {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|x86.ActiveCfg = Debug|Any CPU + {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|x86.Build.0 = Debug|Any CPU {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|Any CPU.ActiveCfg = Release|Any CPU {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|Any CPU.Build.0 = Release|Any CPU + {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|x64.ActiveCfg = Release|Any CPU + {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|x64.Build.0 = Release|Any CPU + {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|x86.ActiveCfg = Release|Any CPU + {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|x86.Build.0 = Release|Any CPU {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|x64.ActiveCfg = Debug|Any CPU + {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|x64.Build.0 = Debug|Any CPU + {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|x86.ActiveCfg = Debug|Any CPU + {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|x86.Build.0 = Debug|Any CPU {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|Any CPU.Build.0 = Release|Any CPU + {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|x64.ActiveCfg = Release|Any CPU + {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|x64.Build.0 = Release|Any CPU + {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|x86.ActiveCfg = Release|Any CPU + {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|x86.Build.0 = Release|Any CPU {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|x64.ActiveCfg = Debug|Any CPU + {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|x64.Build.0 = Debug|Any CPU + {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|x86.ActiveCfg = Debug|Any CPU + {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|x86.Build.0 = Debug|Any CPU {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|Any CPU.Build.0 = Release|Any CPU + {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|x64.ActiveCfg = Release|Any CPU + {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|x64.Build.0 = Release|Any CPU + {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|x86.ActiveCfg = Release|Any CPU + {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|x86.Build.0 = Release|Any CPU {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|x64.ActiveCfg = Debug|Any CPU + {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|x64.Build.0 = Debug|Any CPU + {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|x86.ActiveCfg = Debug|Any CPU + {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|x86.Build.0 = Debug|Any CPU {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|Any CPU.Build.0 = Release|Any CPU + {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|x64.ActiveCfg = Release|Any CPU + {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|x64.Build.0 = Release|Any CPU + {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|x86.ActiveCfg = Release|Any CPU + {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|x86.Build.0 = Release|Any CPU {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|x64.Build.0 = Debug|Any CPU + {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|x86.ActiveCfg = Debug|Any CPU + {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|x86.Build.0 = Debug|Any CPU {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|Any CPU.Build.0 = Release|Any CPU + {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|x64.ActiveCfg = Release|Any CPU + {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|x64.Build.0 = Release|Any CPU + {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|x86.ActiveCfg = Release|Any CPU + {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|x86.Build.0 = Release|Any CPU {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|x64.ActiveCfg = Debug|Any CPU + {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|x64.Build.0 = Debug|Any CPU + {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|x86.ActiveCfg = Debug|Any CPU + {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|x86.Build.0 = Debug|Any CPU {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|Any CPU.Build.0 = Release|Any CPU + {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|x64.ActiveCfg = Release|Any CPU + {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|x64.Build.0 = Release|Any CPU + {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|x86.ActiveCfg = Release|Any CPU + {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|x86.Build.0 = Release|Any CPU {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|x64.ActiveCfg = Debug|Any CPU + {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|x64.Build.0 = Debug|Any CPU + {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|x86.ActiveCfg = Debug|Any CPU + {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|x86.Build.0 = Debug|Any CPU {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|Any CPU.ActiveCfg = Release|Any CPU {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|Any CPU.Build.0 = Release|Any CPU + {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|x64.ActiveCfg = Release|Any CPU + {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|x64.Build.0 = Release|Any CPU + {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|x86.ActiveCfg = Release|Any CPU + {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|x86.Build.0 = Release|Any CPU {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|x64.ActiveCfg = Debug|Any CPU + {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|x64.Build.0 = Debug|Any CPU + {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|x86.ActiveCfg = Debug|Any CPU + {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|x86.Build.0 = Debug|Any CPU {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|Any CPU.ActiveCfg = Release|Any CPU {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|Any CPU.Build.0 = Release|Any CPU + {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|x64.ActiveCfg = Release|Any CPU + {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|x64.Build.0 = Release|Any CPU + {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|x86.ActiveCfg = Release|Any CPU + {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|x86.Build.0 = Release|Any CPU {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|x64.ActiveCfg = Debug|Any CPU + {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|x64.Build.0 = Debug|Any CPU + {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|x86.ActiveCfg = Debug|Any CPU + {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|x86.Build.0 = Debug|Any CPU {86614CB9-0768-40BF-8C27-699E3990B733}.Release|Any CPU.ActiveCfg = Release|Any CPU {86614CB9-0768-40BF-8C27-699E3990B733}.Release|Any CPU.Build.0 = Release|Any CPU + {86614CB9-0768-40BF-8C27-699E3990B733}.Release|x64.ActiveCfg = Release|Any CPU + {86614CB9-0768-40BF-8C27-699E3990B733}.Release|x64.Build.0 = Release|Any CPU + {86614CB9-0768-40BF-8C27-699E3990B733}.Release|x86.ActiveCfg = Release|Any CPU + {86614CB9-0768-40BF-8C27-699E3990B733}.Release|x86.Build.0 = Release|Any CPU {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|x64.ActiveCfg = Debug|Any CPU + {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|x64.Build.0 = Debug|Any CPU + {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|x86.ActiveCfg = Debug|Any CPU + {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|x86.Build.0 = Debug|Any CPU {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|Any CPU.Build.0 = Release|Any CPU + {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|x64.ActiveCfg = Release|Any CPU + {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|x64.Build.0 = Release|Any CPU + {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|x86.ActiveCfg = Release|Any CPU + {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|x86.Build.0 = Release|Any CPU {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|x64.ActiveCfg = Debug|Any CPU + {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|x64.Build.0 = Debug|Any CPU + {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|x86.ActiveCfg = Debug|Any CPU + {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|x86.Build.0 = Debug|Any CPU {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|Any CPU.ActiveCfg = Release|Any CPU {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|Any CPU.Build.0 = Release|Any CPU + {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|x64.ActiveCfg = Release|Any CPU + {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|x64.Build.0 = Release|Any CPU + {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|x86.ActiveCfg = Release|Any CPU + {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|x86.Build.0 = Release|Any CPU {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|x64.Build.0 = Debug|Any CPU + {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|x86.ActiveCfg = Debug|Any CPU + {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|x86.Build.0 = Debug|Any CPU {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|Any CPU.Build.0 = Release|Any CPU + {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|x64.ActiveCfg = Release|Any CPU + {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|x64.Build.0 = Release|Any CPU + {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|x86.ActiveCfg = Release|Any CPU + {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|x86.Build.0 = Release|Any CPU {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|x64.ActiveCfg = Debug|Any CPU + {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|x64.Build.0 = Debug|Any CPU + {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|x86.ActiveCfg = Debug|Any CPU + {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|x86.Build.0 = Debug|Any CPU {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|Any CPU.ActiveCfg = Release|Any CPU {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|Any CPU.Build.0 = Release|Any CPU + {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|x64.ActiveCfg = Release|Any CPU + {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|x64.Build.0 = Release|Any CPU + {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|x86.ActiveCfg = Release|Any CPU + {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|x86.Build.0 = Release|Any CPU {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|x64.Build.0 = Debug|Any CPU + {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|x86.Build.0 = Debug|Any CPU {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|Any CPU.Build.0 = Release|Any CPU + {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|x64.ActiveCfg = Release|Any CPU + {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|x64.Build.0 = Release|Any CPU + {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|x86.ActiveCfg = Release|Any CPU + {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|x86.Build.0 = Release|Any CPU {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|x64.ActiveCfg = Debug|Any CPU + {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|x64.Build.0 = Debug|Any CPU + {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|x86.ActiveCfg = Debug|Any CPU + {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|x86.Build.0 = Debug|Any CPU {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|Any CPU.ActiveCfg = Release|Any CPU {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|Any CPU.Build.0 = Release|Any CPU + {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|x64.ActiveCfg = Release|Any CPU + {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|x64.Build.0 = Release|Any CPU + {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|x86.ActiveCfg = Release|Any CPU + {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|x86.Build.0 = Release|Any CPU {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|x64.ActiveCfg = Debug|Any CPU + {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|x64.Build.0 = Debug|Any CPU + {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|x86.ActiveCfg = Debug|Any CPU + {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|x86.Build.0 = Debug|Any CPU {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|Any CPU.Build.0 = Release|Any CPU + {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|x64.ActiveCfg = Release|Any CPU + {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|x64.Build.0 = Release|Any CPU + {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|x86.ActiveCfg = Release|Any CPU + {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|x86.Build.0 = Release|Any CPU {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|x64.ActiveCfg = Debug|Any CPU + {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|x64.Build.0 = Debug|Any CPU + {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|x86.ActiveCfg = Debug|Any CPU + {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|x86.Build.0 = Debug|Any CPU {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|Any CPU.ActiveCfg = Release|Any CPU {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|Any CPU.Build.0 = Release|Any CPU + {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|x64.ActiveCfg = Release|Any CPU + {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|x64.Build.0 = Release|Any CPU + {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|x86.ActiveCfg = Release|Any CPU + {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|x86.Build.0 = Release|Any CPU {5325536E-8E3A-4611-AB92-B03369493354}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5325536E-8E3A-4611-AB92-B03369493354}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5325536E-8E3A-4611-AB92-B03369493354}.Debug|x64.ActiveCfg = Debug|Any CPU + {5325536E-8E3A-4611-AB92-B03369493354}.Debug|x64.Build.0 = Debug|Any CPU + {5325536E-8E3A-4611-AB92-B03369493354}.Debug|x86.ActiveCfg = Debug|Any CPU + {5325536E-8E3A-4611-AB92-B03369493354}.Debug|x86.Build.0 = Debug|Any CPU {5325536E-8E3A-4611-AB92-B03369493354}.Release|Any CPU.ActiveCfg = Release|Any CPU {5325536E-8E3A-4611-AB92-B03369493354}.Release|Any CPU.Build.0 = Release|Any CPU + {5325536E-8E3A-4611-AB92-B03369493354}.Release|x64.ActiveCfg = Release|Any CPU + {5325536E-8E3A-4611-AB92-B03369493354}.Release|x64.Build.0 = Release|Any CPU + {5325536E-8E3A-4611-AB92-B03369493354}.Release|x86.ActiveCfg = Release|Any CPU + {5325536E-8E3A-4611-AB92-B03369493354}.Release|x86.Build.0 = Release|Any CPU {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|x64.ActiveCfg = Debug|Any CPU + {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|x64.Build.0 = Debug|Any CPU + {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|x86.ActiveCfg = Debug|Any CPU + {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|x86.Build.0 = Debug|Any CPU {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|Any CPU.ActiveCfg = Release|Any CPU {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|Any CPU.Build.0 = Release|Any CPU + {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|x64.ActiveCfg = Release|Any CPU + {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|x64.Build.0 = Release|Any CPU + {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|x86.ActiveCfg = Release|Any CPU + {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|x86.Build.0 = Release|Any CPU {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|x64.ActiveCfg = Debug|Any CPU + {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|x64.Build.0 = Debug|Any CPU + {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|x86.ActiveCfg = Debug|Any CPU + {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|x86.Build.0 = Debug|Any CPU {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|Any CPU.ActiveCfg = Release|Any CPU {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|Any CPU.Build.0 = Release|Any CPU + {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|x64.ActiveCfg = Release|Any CPU + {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|x64.Build.0 = Release|Any CPU + {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|x86.ActiveCfg = Release|Any CPU + {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|x86.Build.0 = Release|Any CPU {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|x64.ActiveCfg = Debug|Any CPU + {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|x64.Build.0 = Debug|Any CPU + {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|x86.ActiveCfg = Debug|Any CPU + {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|x86.Build.0 = Debug|Any CPU {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|Any CPU.ActiveCfg = Release|Any CPU {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|Any CPU.Build.0 = Release|Any CPU + {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|x64.ActiveCfg = Release|Any CPU + {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|x64.Build.0 = Release|Any CPU + {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|x86.ActiveCfg = Release|Any CPU + {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|x86.Build.0 = Release|Any CPU {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|x64.ActiveCfg = Debug|Any CPU + {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|x64.Build.0 = Debug|Any CPU + {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|x86.ActiveCfg = Debug|Any CPU + {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|x86.Build.0 = Debug|Any CPU {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|Any CPU.ActiveCfg = Release|Any CPU {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|Any CPU.Build.0 = Release|Any CPU + {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|x64.ActiveCfg = Release|Any CPU + {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|x64.Build.0 = Release|Any CPU + {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|x86.ActiveCfg = Release|Any CPU + {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|x86.Build.0 = Release|Any CPU {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|x64.Build.0 = Debug|Any CPU + {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|x86.ActiveCfg = Debug|Any CPU + {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|x86.Build.0 = Debug|Any CPU {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|Any CPU.Build.0 = Release|Any CPU + {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|x64.ActiveCfg = Release|Any CPU + {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|x64.Build.0 = Release|Any CPU + {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|x86.ActiveCfg = Release|Any CPU + {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|x86.Build.0 = Release|Any CPU {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|x64.ActiveCfg = Debug|Any CPU + {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|x64.Build.0 = Debug|Any CPU + {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|x86.ActiveCfg = Debug|Any CPU + {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|x86.Build.0 = Debug|Any CPU {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|Any CPU.ActiveCfg = Release|Any CPU {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|Any CPU.Build.0 = Release|Any CPU + {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|x64.ActiveCfg = Release|Any CPU + {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|x64.Build.0 = Release|Any CPU + {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|x86.ActiveCfg = Release|Any CPU + {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|x86.Build.0 = Release|Any CPU {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|x64.ActiveCfg = Debug|Any CPU + {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|x64.Build.0 = Debug|Any CPU + {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|x86.ActiveCfg = Debug|Any CPU + {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|x86.Build.0 = Debug|Any CPU {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|Any CPU.Build.0 = Release|Any CPU + {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|x64.ActiveCfg = Release|Any CPU + {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|x64.Build.0 = Release|Any CPU + {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|x86.ActiveCfg = Release|Any CPU + {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|x86.Build.0 = Release|Any CPU {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|x64.ActiveCfg = Debug|Any CPU + {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|x64.Build.0 = Debug|Any CPU + {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|x86.ActiveCfg = Debug|Any CPU + {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|x86.Build.0 = Debug|Any CPU {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|Any CPU.Build.0 = Release|Any CPU + {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|x64.ActiveCfg = Release|Any CPU + {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|x64.Build.0 = Release|Any CPU + {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|x86.ActiveCfg = Release|Any CPU + {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|x86.Build.0 = Release|Any CPU {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|x64.ActiveCfg = Debug|Any CPU + {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|x64.Build.0 = Debug|Any CPU + {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|x86.ActiveCfg = Debug|Any CPU + {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|x86.Build.0 = Debug|Any CPU {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|Any CPU.ActiveCfg = Release|Any CPU {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|Any CPU.Build.0 = Release|Any CPU + {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|x64.ActiveCfg = Release|Any CPU + {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|x64.Build.0 = Release|Any CPU + {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|x86.ActiveCfg = Release|Any CPU + {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|x86.Build.0 = Release|Any CPU {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|x64.ActiveCfg = Debug|Any CPU + {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|x64.Build.0 = Debug|Any CPU + {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|x86.ActiveCfg = Debug|Any CPU + {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|x86.Build.0 = Debug|Any CPU {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|Any CPU.ActiveCfg = Release|Any CPU {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|Any CPU.Build.0 = Release|Any CPU + {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|x64.ActiveCfg = Release|Any CPU + {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|x64.Build.0 = Release|Any CPU + {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|x86.ActiveCfg = Release|Any CPU + {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|x86.Build.0 = Release|Any CPU {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|x64.ActiveCfg = Debug|Any CPU + {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|x64.Build.0 = Debug|Any CPU + {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|x86.ActiveCfg = Debug|Any CPU + {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|x86.Build.0 = Debug|Any CPU {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|Any CPU.Build.0 = Release|Any CPU + {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|x64.ActiveCfg = Release|Any CPU + {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|x64.Build.0 = Release|Any CPU + {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|x86.ActiveCfg = Release|Any CPU + {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|x86.Build.0 = Release|Any CPU {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|x64.ActiveCfg = Debug|Any CPU + {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|x64.Build.0 = Debug|Any CPU + {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|x86.Build.0 = Debug|Any CPU {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|Any CPU.Build.0 = Release|Any CPU + {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|x64.ActiveCfg = Release|Any CPU + {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|x64.Build.0 = Release|Any CPU + {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|x86.ActiveCfg = Release|Any CPU + {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|x86.Build.0 = Release|Any CPU {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|x64.ActiveCfg = Debug|Any CPU + {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|x64.Build.0 = Debug|Any CPU + {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|x86.ActiveCfg = Debug|Any CPU + {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|x86.Build.0 = Debug|Any CPU {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|Any CPU.ActiveCfg = Release|Any CPU {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|Any CPU.Build.0 = Release|Any CPU + {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|x64.ActiveCfg = Release|Any CPU + {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|x64.Build.0 = Release|Any CPU + {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|x86.ActiveCfg = Release|Any CPU + {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|x86.Build.0 = Release|Any CPU {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|x64.ActiveCfg = Debug|Any CPU + {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|x64.Build.0 = Debug|Any CPU + {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|x86.ActiveCfg = Debug|Any CPU + {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|x86.Build.0 = Debug|Any CPU {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|Any CPU.Build.0 = Release|Any CPU + {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|x64.ActiveCfg = Release|Any CPU + {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|x64.Build.0 = Release|Any CPU + {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|x86.ActiveCfg = Release|Any CPU + {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|x86.Build.0 = Release|Any CPU {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|x64.ActiveCfg = Debug|Any CPU + {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|x64.Build.0 = Debug|Any CPU + {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|x86.ActiveCfg = Debug|Any CPU + {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|x86.Build.0 = Debug|Any CPU {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|Any CPU.ActiveCfg = Release|Any CPU {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|Any CPU.Build.0 = Release|Any CPU + {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|x64.ActiveCfg = Release|Any CPU + {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|x64.Build.0 = Release|Any CPU + {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|x86.ActiveCfg = Release|Any CPU + {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|x86.Build.0 = Release|Any CPU {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|x64.Build.0 = Debug|Any CPU + {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|x86.Build.0 = Debug|Any CPU {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|Any CPU.Build.0 = Release|Any CPU + {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|x64.ActiveCfg = Release|Any CPU + {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|x64.Build.0 = Release|Any CPU + {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|x86.ActiveCfg = Release|Any CPU + {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|x86.Build.0 = Release|Any CPU {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|x64.ActiveCfg = Debug|Any CPU + {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|x64.Build.0 = Debug|Any CPU + {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|x86.ActiveCfg = Debug|Any CPU + {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|x86.Build.0 = Debug|Any CPU {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|Any CPU.Build.0 = Release|Any CPU + {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|x64.ActiveCfg = Release|Any CPU + {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|x64.Build.0 = Release|Any CPU + {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|x86.ActiveCfg = Release|Any CPU + {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|x86.Build.0 = Release|Any CPU {839267B9-492D-47B6-A6AF-454282142123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {839267B9-492D-47B6-A6AF-454282142123}.Debug|Any CPU.Build.0 = Debug|Any CPU + {839267B9-492D-47B6-A6AF-454282142123}.Debug|x64.ActiveCfg = Debug|Any CPU + {839267B9-492D-47B6-A6AF-454282142123}.Debug|x64.Build.0 = Debug|Any CPU + {839267B9-492D-47B6-A6AF-454282142123}.Debug|x86.ActiveCfg = Debug|Any CPU + {839267B9-492D-47B6-A6AF-454282142123}.Debug|x86.Build.0 = Debug|Any CPU {839267B9-492D-47B6-A6AF-454282142123}.Release|Any CPU.ActiveCfg = Release|Any CPU {839267B9-492D-47B6-A6AF-454282142123}.Release|Any CPU.Build.0 = Release|Any CPU + {839267B9-492D-47B6-A6AF-454282142123}.Release|x64.ActiveCfg = Release|Any CPU + {839267B9-492D-47B6-A6AF-454282142123}.Release|x64.Build.0 = Release|Any CPU + {839267B9-492D-47B6-A6AF-454282142123}.Release|x86.ActiveCfg = Release|Any CPU + {839267B9-492D-47B6-A6AF-454282142123}.Release|x86.Build.0 = Release|Any CPU {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|x64.ActiveCfg = Debug|Any CPU + {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|x64.Build.0 = Debug|Any CPU + {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|x86.ActiveCfg = Debug|Any CPU + {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|x86.Build.0 = Debug|Any CPU {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|Any CPU.ActiveCfg = Release|Any CPU {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|Any CPU.Build.0 = Release|Any CPU + {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|x64.ActiveCfg = Release|Any CPU + {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|x64.Build.0 = Release|Any CPU + {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|x86.ActiveCfg = Release|Any CPU + {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|x86.Build.0 = Release|Any CPU {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|x64.ActiveCfg = Debug|Any CPU + {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|x64.Build.0 = Debug|Any CPU + {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|x86.ActiveCfg = Debug|Any CPU + {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|x86.Build.0 = Debug|Any CPU {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|Any CPU.Build.0 = Release|Any CPU + {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|x64.ActiveCfg = Release|Any CPU + {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|x64.Build.0 = Release|Any CPU + {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|x86.ActiveCfg = Release|Any CPU + {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|x86.Build.0 = Release|Any CPU {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|x64.ActiveCfg = Debug|Any CPU + {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|x64.Build.0 = Debug|Any CPU + {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|x86.Build.0 = Debug|Any CPU {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|Any CPU.Build.0 = Release|Any CPU + {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|x64.ActiveCfg = Release|Any CPU + {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|x64.Build.0 = Release|Any CPU + {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|x86.ActiveCfg = Release|Any CPU + {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|x86.Build.0 = Release|Any CPU {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|x64.Build.0 = Debug|Any CPU + {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|x86.Build.0 = Debug|Any CPU {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|Any CPU.Build.0 = Release|Any CPU + {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|x64.ActiveCfg = Release|Any CPU + {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|x64.Build.0 = Release|Any CPU + {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|x86.ActiveCfg = Release|Any CPU + {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|x86.Build.0 = Release|Any CPU {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|x64.ActiveCfg = Debug|Any CPU + {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|x64.Build.0 = Debug|Any CPU + {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|x86.ActiveCfg = Debug|Any CPU + {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|x86.Build.0 = Debug|Any CPU {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|x64.ActiveCfg = Release|Any CPU + {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|x64.Build.0 = Release|Any CPU + {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|x86.ActiveCfg = Release|Any CPU + {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|x86.Build.0 = Release|Any CPU {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|x64.ActiveCfg = Debug|Any CPU + {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|x64.Build.0 = Debug|Any CPU + {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|x86.ActiveCfg = Debug|Any CPU + {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|x86.Build.0 = Debug|Any CPU {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|Any CPU.ActiveCfg = Release|Any CPU {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|Any CPU.Build.0 = Release|Any CPU + {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|x64.ActiveCfg = Release|Any CPU + {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|x64.Build.0 = Release|Any CPU + {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|x86.ActiveCfg = Release|Any CPU + {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|x86.Build.0 = Release|Any CPU {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|x64.ActiveCfg = Debug|Any CPU + {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|x64.Build.0 = Debug|Any CPU + {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|x86.ActiveCfg = Debug|Any CPU + {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|x86.Build.0 = Debug|Any CPU {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|Any CPU.ActiveCfg = Release|Any CPU {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|Any CPU.Build.0 = Release|Any CPU + {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|x64.ActiveCfg = Release|Any CPU + {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|x64.Build.0 = Release|Any CPU + {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|x86.ActiveCfg = Release|Any CPU + {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|x86.Build.0 = Release|Any CPU {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|x64.ActiveCfg = Debug|Any CPU + {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|x64.Build.0 = Debug|Any CPU + {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|x86.ActiveCfg = Debug|Any CPU + {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|x86.Build.0 = Debug|Any CPU {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|Any CPU.Build.0 = Release|Any CPU + {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|x64.ActiveCfg = Release|Any CPU + {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|x64.Build.0 = Release|Any CPU + {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|x86.ActiveCfg = Release|Any CPU + {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|x86.Build.0 = Release|Any CPU {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|Any CPU.Build.0 = Debug|Any CPU + {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|x64.ActiveCfg = Debug|Any CPU + {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|x64.Build.0 = Debug|Any CPU + {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|x86.ActiveCfg = Debug|Any CPU + {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|x86.Build.0 = Debug|Any CPU {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|Any CPU.ActiveCfg = Release|Any CPU {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|Any CPU.Build.0 = Release|Any CPU + {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|x64.ActiveCfg = Release|Any CPU + {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|x64.Build.0 = Release|Any CPU + {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|x86.ActiveCfg = Release|Any CPU + {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|x86.Build.0 = Release|Any CPU {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|x64.ActiveCfg = Debug|Any CPU + {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|x64.Build.0 = Debug|Any CPU + {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|x86.ActiveCfg = Debug|Any CPU + {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|x86.Build.0 = Debug|Any CPU {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|Any CPU.Build.0 = Release|Any CPU + {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|x64.ActiveCfg = Release|Any CPU + {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|x64.Build.0 = Release|Any CPU + {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|x86.ActiveCfg = Release|Any CPU + {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|x86.Build.0 = Release|Any CPU {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|x64.Build.0 = Debug|Any CPU + {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|x86.ActiveCfg = Debug|Any CPU + {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|x86.Build.0 = Debug|Any CPU {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|Any CPU.Build.0 = Release|Any CPU + {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|x64.ActiveCfg = Release|Any CPU + {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|x64.Build.0 = Release|Any CPU + {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|x86.ActiveCfg = Release|Any CPU + {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|x86.Build.0 = Release|Any CPU {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|x64.ActiveCfg = Debug|Any CPU + {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|x64.Build.0 = Debug|Any CPU + {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|x86.ActiveCfg = Debug|Any CPU + {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|x86.Build.0 = Debug|Any CPU {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|Any CPU.ActiveCfg = Release|Any CPU {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|Any CPU.Build.0 = Release|Any CPU + {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|x64.ActiveCfg = Release|Any CPU + {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|x64.Build.0 = Release|Any CPU + {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|x86.ActiveCfg = Release|Any CPU + {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|x86.Build.0 = Release|Any CPU {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|x64.Build.0 = Debug|Any CPU + {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|x86.Build.0 = Debug|Any CPU {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|Any CPU.Build.0 = Release|Any CPU + {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|x64.ActiveCfg = Release|Any CPU + {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|x64.Build.0 = Release|Any CPU + {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|x86.ActiveCfg = Release|Any CPU + {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|x86.Build.0 = Release|Any CPU {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|x64.ActiveCfg = Debug|Any CPU + {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|x64.Build.0 = Debug|Any CPU + {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|x86.ActiveCfg = Debug|Any CPU + {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|x86.Build.0 = Debug|Any CPU {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|Any CPU.ActiveCfg = Release|Any CPU {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|Any CPU.Build.0 = Release|Any CPU + {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|x64.ActiveCfg = Release|Any CPU + {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|x64.Build.0 = Release|Any CPU + {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|x86.ActiveCfg = Release|Any CPU + {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|x86.Build.0 = Release|Any CPU {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|x64.ActiveCfg = Debug|Any CPU + {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|x64.Build.0 = Debug|Any CPU + {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|x86.ActiveCfg = Debug|Any CPU + {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|x86.Build.0 = Debug|Any CPU {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|Any CPU.ActiveCfg = Release|Any CPU {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|Any CPU.Build.0 = Release|Any CPU + {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|x64.ActiveCfg = Release|Any CPU + {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|x64.Build.0 = Release|Any CPU + {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|x86.ActiveCfg = Release|Any CPU + {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|x86.Build.0 = Release|Any CPU {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|x64.ActiveCfg = Debug|Any CPU + {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|x64.Build.0 = Debug|Any CPU + {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|x86.ActiveCfg = Debug|Any CPU + {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|x86.Build.0 = Debug|Any CPU {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|Any CPU.ActiveCfg = Release|Any CPU {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|Any CPU.Build.0 = Release|Any CPU + {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|x64.ActiveCfg = Release|Any CPU + {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|x64.Build.0 = Release|Any CPU + {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|x86.ActiveCfg = Release|Any CPU + {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|x86.Build.0 = Release|Any CPU {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|x64.ActiveCfg = Debug|Any CPU + {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|x64.Build.0 = Debug|Any CPU + {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|x86.ActiveCfg = Debug|Any CPU + {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|x86.Build.0 = Debug|Any CPU {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|Any CPU.ActiveCfg = Release|Any CPU {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|Any CPU.Build.0 = Release|Any CPU + {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|x64.ActiveCfg = Release|Any CPU + {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|x64.Build.0 = Release|Any CPU + {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|x86.ActiveCfg = Release|Any CPU + {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|x86.Build.0 = Release|Any CPU {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|x64.ActiveCfg = Debug|Any CPU + {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|x64.Build.0 = Debug|Any CPU + {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|x86.ActiveCfg = Debug|Any CPU + {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|x86.Build.0 = Debug|Any CPU {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|Any CPU.Build.0 = Release|Any CPU + {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|x64.ActiveCfg = Release|Any CPU + {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|x64.Build.0 = Release|Any CPU + {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|x86.ActiveCfg = Release|Any CPU + {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|x86.Build.0 = Release|Any CPU {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|x64.ActiveCfg = Debug|Any CPU + {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|x64.Build.0 = Debug|Any CPU + {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|x86.ActiveCfg = Debug|Any CPU + {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|x86.Build.0 = Debug|Any CPU {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|Any CPU.Build.0 = Release|Any CPU + {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|x64.ActiveCfg = Release|Any CPU + {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|x64.Build.0 = Release|Any CPU + {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|x86.ActiveCfg = Release|Any CPU + {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|x86.Build.0 = Release|Any CPU {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|x64.ActiveCfg = Debug|Any CPU + {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|x64.Build.0 = Debug|Any CPU + {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|x86.ActiveCfg = Debug|Any CPU + {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|x86.Build.0 = Debug|Any CPU {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|Any CPU.Build.0 = Release|Any CPU + {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|x64.ActiveCfg = Release|Any CPU + {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|x64.Build.0 = Release|Any CPU + {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|x86.ActiveCfg = Release|Any CPU + {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|x86.Build.0 = Release|Any CPU {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|x64.ActiveCfg = Debug|Any CPU + {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|x64.Build.0 = Debug|Any CPU + {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|x86.ActiveCfg = Debug|Any CPU + {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|x86.Build.0 = Debug|Any CPU {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|Any CPU.ActiveCfg = Release|Any CPU {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|Any CPU.Build.0 = Release|Any CPU + {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|x64.ActiveCfg = Release|Any CPU + {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|x64.Build.0 = Release|Any CPU + {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|x86.ActiveCfg = Release|Any CPU + {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|x86.Build.0 = Release|Any CPU {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|x64.ActiveCfg = Debug|Any CPU + {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|x64.Build.0 = Debug|Any CPU + {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|x86.ActiveCfg = Debug|Any CPU + {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|x86.Build.0 = Debug|Any CPU {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|Any CPU.Build.0 = Release|Any CPU + {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|x64.ActiveCfg = Release|Any CPU + {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|x64.Build.0 = Release|Any CPU + {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|x86.ActiveCfg = Release|Any CPU + {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|x86.Build.0 = Release|Any CPU {101681FB-569F-4941-B943-2AD380039BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {101681FB-569F-4941-B943-2AD380039BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {101681FB-569F-4941-B943-2AD380039BE0}.Debug|x64.ActiveCfg = Debug|Any CPU + {101681FB-569F-4941-B943-2AD380039BE0}.Debug|x64.Build.0 = Debug|Any CPU + {101681FB-569F-4941-B943-2AD380039BE0}.Debug|x86.ActiveCfg = Debug|Any CPU + {101681FB-569F-4941-B943-2AD380039BE0}.Debug|x86.Build.0 = Debug|Any CPU {101681FB-569F-4941-B943-2AD380039BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU {101681FB-569F-4941-B943-2AD380039BE0}.Release|Any CPU.Build.0 = Release|Any CPU + {101681FB-569F-4941-B943-2AD380039BE0}.Release|x64.ActiveCfg = Release|Any CPU + {101681FB-569F-4941-B943-2AD380039BE0}.Release|x64.Build.0 = Release|Any CPU + {101681FB-569F-4941-B943-2AD380039BE0}.Release|x86.ActiveCfg = Release|Any CPU + {101681FB-569F-4941-B943-2AD380039BE0}.Release|x86.Build.0 = Release|Any CPU {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|x64.ActiveCfg = Debug|Any CPU + {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|x64.Build.0 = Debug|Any CPU + {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|x86.ActiveCfg = Debug|Any CPU + {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|x86.Build.0 = Debug|Any CPU {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|Any CPU.Build.0 = Release|Any CPU + {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|x64.ActiveCfg = Release|Any CPU + {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|x64.Build.0 = Release|Any CPU + {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|x86.ActiveCfg = Release|Any CPU + {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|x86.Build.0 = Release|Any CPU {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|x64.ActiveCfg = Debug|Any CPU + {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|x64.Build.0 = Debug|Any CPU + {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|x86.ActiveCfg = Debug|Any CPU + {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|x86.Build.0 = Debug|Any CPU {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|Any CPU.ActiveCfg = Release|Any CPU {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|Any CPU.Build.0 = Release|Any CPU + {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|x64.ActiveCfg = Release|Any CPU + {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|x64.Build.0 = Release|Any CPU + {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|x86.ActiveCfg = Release|Any CPU + {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|x86.Build.0 = Release|Any CPU {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|x64.ActiveCfg = Debug|Any CPU + {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|x64.Build.0 = Debug|Any CPU + {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|x86.ActiveCfg = Debug|Any CPU + {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|x86.Build.0 = Debug|Any CPU {A4DFC94C-0769-41B3-926C-22642185C878}.Release|Any CPU.ActiveCfg = Release|Any CPU {A4DFC94C-0769-41B3-926C-22642185C878}.Release|Any CPU.Build.0 = Release|Any CPU + {A4DFC94C-0769-41B3-926C-22642185C878}.Release|x64.ActiveCfg = Release|Any CPU + {A4DFC94C-0769-41B3-926C-22642185C878}.Release|x64.Build.0 = Release|Any CPU + {A4DFC94C-0769-41B3-926C-22642185C878}.Release|x86.ActiveCfg = Release|Any CPU + {A4DFC94C-0769-41B3-926C-22642185C878}.Release|x86.Build.0 = Release|Any CPU {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|x64.ActiveCfg = Debug|Any CPU + {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|x64.Build.0 = Debug|Any CPU + {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|x86.ActiveCfg = Debug|Any CPU + {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|x86.Build.0 = Debug|Any CPU {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|Any CPU.ActiveCfg = Release|Any CPU {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|Any CPU.Build.0 = Release|Any CPU + {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|x64.ActiveCfg = Release|Any CPU + {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|x64.Build.0 = Release|Any CPU + {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|x86.ActiveCfg = Release|Any CPU + {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|x86.Build.0 = Release|Any CPU {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|x64.ActiveCfg = Debug|Any CPU + {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|x64.Build.0 = Debug|Any CPU + {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|x86.ActiveCfg = Debug|Any CPU + {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|x86.Build.0 = Debug|Any CPU {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|Any CPU.Build.0 = Release|Any CPU + {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|x64.ActiveCfg = Release|Any CPU + {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|x64.Build.0 = Release|Any CPU + {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|x86.ActiveCfg = Release|Any CPU + {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|x86.Build.0 = Release|Any CPU {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|x64.ActiveCfg = Debug|Any CPU + {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|x64.Build.0 = Debug|Any CPU + {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|x86.Build.0 = Debug|Any CPU {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|Any CPU.Build.0 = Release|Any CPU + {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|x64.ActiveCfg = Release|Any CPU + {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|x64.Build.0 = Release|Any CPU + {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|x86.ActiveCfg = Release|Any CPU + {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|x86.Build.0 = Release|Any CPU {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|x64.ActiveCfg = Debug|Any CPU + {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|x64.Build.0 = Debug|Any CPU + {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|x86.ActiveCfg = Debug|Any CPU + {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|x86.Build.0 = Debug|Any CPU {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|Any CPU.Build.0 = Release|Any CPU + {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|x64.ActiveCfg = Release|Any CPU + {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|x64.Build.0 = Release|Any CPU + {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|x86.ActiveCfg = Release|Any CPU + {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|x86.Build.0 = Release|Any CPU {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|x64.Build.0 = Debug|Any CPU + {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|x86.Build.0 = Debug|Any CPU {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|Any CPU.Build.0 = Release|Any CPU + {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|x64.ActiveCfg = Release|Any CPU + {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|x64.Build.0 = Release|Any CPU + {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|x86.ActiveCfg = Release|Any CPU + {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|x86.Build.0 = Release|Any CPU {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|x64.ActiveCfg = Debug|Any CPU + {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|x64.Build.0 = Debug|Any CPU + {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|x86.ActiveCfg = Debug|Any CPU + {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|x86.Build.0 = Debug|Any CPU {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|Any CPU.Build.0 = Release|Any CPU + {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|x64.ActiveCfg = Release|Any CPU + {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|x64.Build.0 = Release|Any CPU + {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|x86.ActiveCfg = Release|Any CPU + {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|x86.Build.0 = Release|Any CPU {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|x64.ActiveCfg = Debug|Any CPU + {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|x64.Build.0 = Debug|Any CPU + {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|x86.ActiveCfg = Debug|Any CPU + {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|x86.Build.0 = Debug|Any CPU {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|Any CPU.Build.0 = Release|Any CPU + {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|x64.ActiveCfg = Release|Any CPU + {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|x64.Build.0 = Release|Any CPU + {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|x86.ActiveCfg = Release|Any CPU + {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|x86.Build.0 = Release|Any CPU {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|x64.ActiveCfg = Debug|Any CPU + {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|x64.Build.0 = Debug|Any CPU + {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|x86.ActiveCfg = Debug|Any CPU + {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|x86.Build.0 = Debug|Any CPU {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|Any CPU.Build.0 = Release|Any CPU + {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|x64.ActiveCfg = Release|Any CPU + {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|x64.Build.0 = Release|Any CPU + {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|x86.ActiveCfg = Release|Any CPU + {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|x86.Build.0 = Release|Any CPU {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|x64.ActiveCfg = Debug|Any CPU + {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|x64.Build.0 = Debug|Any CPU + {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|x86.ActiveCfg = Debug|Any CPU + {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|x86.Build.0 = Debug|Any CPU {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|Any CPU.Build.0 = Release|Any CPU + {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|x64.ActiveCfg = Release|Any CPU + {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|x64.Build.0 = Release|Any CPU + {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|x86.ActiveCfg = Release|Any CPU + {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|x86.Build.0 = Release|Any CPU {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|x64.ActiveCfg = Debug|Any CPU + {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|x64.Build.0 = Debug|Any CPU + {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|x86.ActiveCfg = Debug|Any CPU + {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|x86.Build.0 = Debug|Any CPU {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|Any CPU.Build.0 = Release|Any CPU + {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|x64.ActiveCfg = Release|Any CPU + {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|x64.Build.0 = Release|Any CPU + {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|x86.ActiveCfg = Release|Any CPU + {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|x86.Build.0 = Release|Any CPU {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|x64.ActiveCfg = Debug|Any CPU + {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|x64.Build.0 = Debug|Any CPU + {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|x86.ActiveCfg = Debug|Any CPU + {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|x86.Build.0 = Debug|Any CPU {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|Any CPU.Build.0 = Release|Any CPU + {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|x64.ActiveCfg = Release|Any CPU + {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|x64.Build.0 = Release|Any CPU + {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|x86.ActiveCfg = Release|Any CPU + {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|x86.Build.0 = Release|Any CPU {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|x64.Build.0 = Debug|Any CPU + {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|x86.ActiveCfg = Debug|Any CPU + {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|x86.Build.0 = Debug|Any CPU {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|Any CPU.Build.0 = Release|Any CPU + {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|x64.ActiveCfg = Release|Any CPU + {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|x64.Build.0 = Release|Any CPU + {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|x86.ActiveCfg = Release|Any CPU + {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|x86.Build.0 = Release|Any CPU {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|x64.Build.0 = Debug|Any CPU + {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|x86.ActiveCfg = Debug|Any CPU + {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|x86.Build.0 = Debug|Any CPU {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|Any CPU.ActiveCfg = Release|Any CPU {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|Any CPU.Build.0 = Release|Any CPU + {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|x64.ActiveCfg = Release|Any CPU + {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|x64.Build.0 = Release|Any CPU + {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|x86.ActiveCfg = Release|Any CPU + {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|x86.Build.0 = Release|Any CPU {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|x64.ActiveCfg = Debug|Any CPU + {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|x64.Build.0 = Debug|Any CPU + {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|x86.ActiveCfg = Debug|Any CPU + {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|x86.Build.0 = Debug|Any CPU {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|Any CPU.Build.0 = Release|Any CPU + {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|x64.ActiveCfg = Release|Any CPU + {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|x64.Build.0 = Release|Any CPU + {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|x86.ActiveCfg = Release|Any CPU + {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|x86.Build.0 = Release|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|x64.Build.0 = Debug|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|x86.ActiveCfg = Debug|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|x86.Build.0 = Debug|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|Any CPU.Build.0 = Release|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|x64.ActiveCfg = Release|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|x64.Build.0 = Release|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|x86.ActiveCfg = Release|Any CPU + {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -892,6 +1784,7 @@ Global {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D} {8350C405-9E17-4110-B9A8-0AB43A8816B7} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D} {B1F6EA42-7B1B-469E-B304-6B2E6FE39852} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D} + {53B5B8F0-023E-4D2D-84F0-5B68610682A4} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C7B54DE2-6407-4802-AD9C-CE54BF414C8C} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs new file mode 100644 index 000000000..5341140c4 --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs @@ -0,0 +1,18 @@ +// Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers +// for more information concerning the license and the contributors participating to this project. + +namespace AspNet.Security.OAuth.Etsy; + +public enum EtsyAuthenticationAccessType +{ + /// + /// Public client access type aka 'private usage access' in Etsy App Registration. + /// + Public, + + //// + //// Confidential client access type aka 'commercial usage access' in Etsy App Registration. // TODO: Uncomment if someone can verify that commercial usage access supports confidential clients. + //// + // Confidential +} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs index ee43c6a8e..c098e2d19 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers * for more information concerning the license and the contributors participating to this project. @@ -29,6 +29,9 @@ public static class EtsyAuthenticationDefaults /// /// Default value for . /// + /// + /// "/signin-etsy" + /// public static readonly string CallbackPath = "/signin-etsy"; /// @@ -42,7 +45,23 @@ public static class EtsyAuthenticationDefaults public static readonly string TokenEndpoint = "https://api.etsy.com/v3/public/oauth/token"; /// - /// Default value for . + /// Default value for Etsy getMe Endpoint. /// public static readonly string UserInformationEndpoint = "https://openapi.etsy.com/v3/application/users/me"; + + /// + /// Default value for the Etsy v3 API Base URI + /// + /// + /// https://openapi.etsy.com/v3/application/ + /// + public static readonly string EtsyBaseUri = "https://openapi.etsy.com/v3/application/"; + + /// + /// Default value for Etsy user details endpoint path getUser. + /// + /// + /// "/users/{user_id}" + /// + public static readonly string UserDetailsPath = "/users/{user_id}"; } diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs index a515cf39c..231205fdd 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers * for more information concerning the license and the contributors participating to this project. @@ -71,7 +71,6 @@ public static AuthenticationBuilder AddEtsy( [CanBeNull] string caption, [NotNull] Action configuration) { - builder.Services.TryAddSingleton, EtsyPostConfigureOptions>(); return builder.AddOAuth(scheme, caption, configuration); } } diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs index 5473a9673..898177289 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers * for more information concerning the license and the contributors participating to this project. @@ -6,6 +6,7 @@ using System.Globalization; using System.Net.Http.Headers; +using System.Net.Mime; using System.Security.Claims; using System.Text.Encodings.Web; using System.Text.Json; @@ -29,9 +30,9 @@ protected override async Task CreateTicketAsync( [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens) { - // First, get the basic user info and shop_id from /v3/application/users/me + // Get the basic user info (user_id and shop_id) using var meRequest = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint); - meRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + meRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); meRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); meRequest.Headers.Add("x-api-key", Options.ClientId); @@ -50,41 +51,43 @@ protected override async Task CreateTicketAsync( var userId = meRoot.GetProperty("user_id").GetInt64(); var shopId = meRoot.GetProperty("shop_id").GetInt64(); - // Add the basic claims from the /me endpoint - // Use shop_id as the primary identifier for Etsy (required for most API operations) - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, shopId.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.String, Options.ClaimsIssuer)); - identity.AddClaim(new Claim(EtsyAuthenticationConstants.Claims.UserId, userId.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.String, Options.ClaimsIssuer)); - identity.AddClaim(new Claim(EtsyAuthenticationConstants.Claims.ShopId, shopId.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.String, Options.ClaimsIssuer)); + var principal = new ClaimsPrincipal(identity); + var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, meRoot); - // Now get additional user details from /v3/application/users/{user_id} - var userDetailEndpoint = $"https://openapi.etsy.com/v3/application/users/{userId}"; - using var userRequest = new HttpRequestMessage(HttpMethod.Get, userDetailEndpoint); - userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + // Map claims from the basic payload first + context.RunClaimActions(); + + // Optionally enrich with detailed user info + if (Options.IncludeDetailedUserInfo) + { + using var detailedPayload = await GetDetailedUserInfoAsync(tokens); + context.RunClaimActions(detailedPayload.RootElement); + } + + await Events.CreatingTicket(context); + return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name); + } + + /// + /// Retrieves detailed user information from Etsy. + /// + /// The OAuth token response. + /// A JSON document containing the detailed user information. + protected virtual async Task GetDetailedUserInfoAsync([NotNull] OAuthTokenResponse tokens) + { + using var userRequest = new HttpRequestMessage(HttpMethod.Get, EtsyAuthenticationDefaults.EtsyBaseUri + EtsyAuthenticationDefaults.UserDetailsPath); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); userRequest.Headers.Add("x-api-key", Options.ClientId); using var userResponse = await Backchannel.SendAsync(userRequest, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); - if (userResponse.IsSuccessStatusCode) + if (!userResponse.IsSuccessStatusCode) { - using var userPayload = JsonDocument.Parse(await userResponse.Content.ReadAsStringAsync(Context.RequestAborted)); - - // Create context with the detailed user data for claim mapping - var principal = new ClaimsPrincipal(identity); - var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, userPayload.RootElement); - context.RunClaimActions(); - - await Events.CreatingTicket(context); - return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name); + await Log.UserProfileErrorAsync(Logger, userResponse, Context.RequestAborted); + throw new HttpRequestException("An error occurred while retrieving detailed user info from Etsy."); } - else - { - // If detailed user info call fails, just create ticket with basic info - var principal = new ClaimsPrincipal(identity); - var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, meRoot); - await Events.CreatingTicket(context); - return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name); - } + return JsonDocument.Parse(await userResponse.Content.ReadAsStringAsync(Context.RequestAborted)); } private static partial class Log diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index 6e3f075a3..d975bc546 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -1,10 +1,11 @@ -/* +/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers * for more information concerning the license and the contributors participating to this project. */ using System.Security.Claims; +using Microsoft.Extensions.Options; using static AspNet.Security.OAuth.Etsy.EtsyAuthenticationConstants; namespace AspNet.Security.OAuth.Etsy; @@ -14,6 +15,8 @@ namespace AspNet.Security.OAuth.Etsy; /// public class EtsyAuthenticationOptions : OAuthOptions { + public bool IncludeDetailedUserInfo { get; set; } + public EtsyAuthenticationOptions() { ClaimsIssuer = EtsyAuthenticationDefaults.Issuer; @@ -29,14 +32,14 @@ public EtsyAuthenticationOptions() // Enable refresh token support SaveTokens = true; - // Default scopes - Etsy requires at least one scope - Scope.Add(Scopes.EmailRead); + // Default scopes - Etsy requires at least one scope and this is the one for basic user info Scope.Add(Scopes.ShopsRead); - // Map Etsy user fields to standard and custom claims - // These mappings apply to the /v3/application/users/{user_id} endpoint response - // Note: ClaimTypes.NameIdentifier, UserId, and ShopId are set programmatically - // in the handler from the /v3/application/users/me endpoint + // Map basic user claims + ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "user_id"); + ClaimActions.MapJsonKey(Claims.ShopId, "shop_id"); + + // Map detailed user claims for detailed user info /v3/application/users/{user_id} ClaimActions.MapJsonKey(ClaimTypes.Email, "primary_email"); ClaimActions.MapJsonKey(ClaimTypes.GivenName, "first_name"); ClaimActions.MapJsonKey(ClaimTypes.Surname, "last_name"); @@ -45,4 +48,77 @@ public EtsyAuthenticationOptions() ClaimActions.MapJsonKey(Claims.LastName, "last_name"); ClaimActions.MapJsonKey(Claims.ImageUrl, "image_url_75x75"); } + + /// + /// Gets or sets the value for the Etsy client's access type. + /// + public EtsyAuthenticationAccessType AccessType { get; set; } + + /// + public override void Validate() + { + try + { + // HACK We want all of the base validation except for ClientSecret, + // so rather than re-implement it all, catch the exception thrown + // for that being null and only throw if we aren't using public access type. + // This does mean that three checks have to be re-implemented + // because the won't be validated if the ClientSecret validation fails. + base.Validate(); + } + catch (ArgumentException ex) when (ex.ParamName == nameof(ClientSecret) && AccessType == EtsyAuthenticationAccessType.Public) + { + // No client secret is required for Etsy API, which uses Authorization Code Flow https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1 with: + // See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues/610. + } + + // Ensure PKCE is enabled (required by Etsy) + if (!UsePkce) + { + throw new ArgumentException("PKCE is required by Etsy Authentication and must be enabled.", nameof(UsePkce)); + } + + if (!SaveTokens) + { + throw new ArgumentException("Saving tokens is required by Etsy Authentication and must be enabled.", nameof(SaveTokens)); + } + + if (string.IsNullOrEmpty(AuthorizationEndpoint)) + { + throw new ArgumentNullException($"The '{nameof(AuthorizationEndpoint)}' option must be provided.", nameof(AuthorizationEndpoint)); + } + + if (string.IsNullOrEmpty(TokenEndpoint)) + { + throw new ArgumentNullException($"The '{nameof(TokenEndpoint)}' option must be provided.", nameof(TokenEndpoint)); + } + + if (string.IsNullOrEmpty(UserInformationEndpoint)) + { + throw new ArgumentNullException($"The '{nameof(UserInformationEndpoint)}' option must be provided.", nameof(UserInformationEndpoint)); + } + + // Ensure at least one scope is requested (required by Etsy) + if (Scope.Count == 0) + { + throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), "At least one scope must be specified for Etsy authentication."); + } + + if (!Scope.Contains(Scopes.ShopsRead)) + { + // ShopsRead scope is required to access basic user info + throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), $"The '{Scopes.ShopsRead}' scope must be specified for Etsy authentication UserInfoEndpoint: https://developers.etsy.com/documentation/reference#operation/getMe"); + } + + if (IncludeDetailedUserInfo && !Scope.Contains(Scopes.EmailRead)) + { + // EmailRead scope is required to access detailed user info + throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), $"The '{Scopes.EmailRead}' scope must be specified for Etsy authentication when '{nameof(IncludeDetailedUserInfo)}' is enabled."); + } + + if (!CallbackPath.HasValue) + { + throw new ArgumentException($"The '{nameof(CallbackPath)}' option must be provided.", nameof(CallbackPath)); + } + } } diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs deleted file mode 100644 index bb19761de..000000000 --- a/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers - * for more information concerning the license and the contributors participating to this project. - */ - -using Microsoft.Extensions.Options; - -namespace AspNet.Security.OAuth.Etsy; - -/// -/// Contains the methods required to ensure that the Etsy configuration is valid. -/// -public class EtsyPostConfigureOptions : IPostConfigureOptions -{ - /// - /// Invoked to post-configure a TOptions instance. - /// - /// The name of the options instance being configured. - /// The options instance to configure. - public void PostConfigure(string? name, EtsyAuthenticationOptions options) - { - if (string.IsNullOrEmpty(options.ClientId)) - { - throw new ArgumentException("The Etsy Client ID cannot be null or empty.", nameof(options)); - } - - // Note: Client Secret validation removed - Etsy uses mandatory PKCE which provides - // cryptographic proof of authorization code ownership, potentially eliminating the - // need for client_secret in the token exchange. The ClientId (keystring) is used - // in the x-api-key header for API authentication. - - // Ensure PKCE is enabled (required by Etsy) - if (!options.UsePkce) - { - throw new ArgumentException("PKCE is required by Etsy and cannot be disabled.", nameof(options)); - } - - // Ensure at least one scope is requested (required by Etsy) - if (options.Scope.Count == 0) - { - throw new ArgumentException("At least one scope must be specified for Etsy authentication.", nameof(options)); - } - } -} From 85f3d60cde312c44e7324613fedbd892274589e7 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Thu, 6 Nov 2025 20:39:02 +0100 Subject: [PATCH 05/32] test(EtsyProvider): Added unit tests for EtsyAuthenticationOptions and IncludeDetailedUserInfo Handler --- .../Etsy/EtsyAuthenticationOptionsTests.cs | 161 ++++++++++++++++++ .../Etsy/EtsyTests.cs | 54 +++++- 2 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs new file mode 100644 index 000000000..fa00cb319 --- /dev/null +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs @@ -0,0 +1,161 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +namespace AspNet.Security.OAuth.Etsy; + +public static class EtsyAuthenticationOptionsTests +{ + public static TheoryData AccessTypes => new() + { + { EtsyAuthenticationAccessType.Public }, // Private Etsy API access does not use client secret (aka 'shared secret' in Etsy App Registration) https://developers.etsy.com/documentation/essentials/authentication + + // { EtsyAuthenticationAccessType.Confidential } // TODO: Verify commercial access app registration Authentication does support confidential clients. Etsy docs do not indicate this to be used at all but support stated this would be required for token refresh + }; + + [Theory] + [InlineData(null, EtsyAuthenticationAccessType.Public)] + [InlineData("", EtsyAuthenticationAccessType.Public)] + public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided_For_Public_Access_Type(string? clientSecret, EtsyAuthenticationAccessType accessType) + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + AccessType = accessType, + ClientId = "my-client-id", + ClientSecret = clientSecret!, + }; + + // Act (no Assert) + options.Validate(); + } + + // Leaving this test commented out to for case that ClientSecret is used with commercial access type - so this can be reactivated + // [Theory] + // [InlineData(EtsyAuthenticationAccessType.Commercial)] + // public static void Validate_Throws_If_ClientSecret_Is_Null(EtsyAuthenticationAccessType accessType) + // { + // // Arrange + // var options = new EtsyAuthenticationOptions() + // { + // AccessType = accessType, + // ClientId = "my-client-id", + // ClientSecret = null!, + // }; + // + // // Act and Assert + // _ = Assert.Throws("ClientSecret", options.Validate); + // } + [Theory] + [InlineData(EtsyAuthenticationAccessType.Public, true, false)] + [InlineData(EtsyAuthenticationAccessType.Public, false, false)] + [InlineData(EtsyAuthenticationAccessType.Public, false, true)] + public static void Validate_Throws_If_SaveTokens_Or_Pkce_Is_Disabled(EtsyAuthenticationAccessType accessType, bool usePkce, bool saveTokens) + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + AccessType = accessType, + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + SaveTokens = saveTokens, + UsePkce = usePkce, + }; + + // Act and Assert + _ = Assert.Throws(options.Validate); + } + + [Theory] + [MemberData(nameof(AccessTypes))] + public static void Validate_Throws_If_Scope_Is_Empty(EtsyAuthenticationAccessType accessType) + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + AccessType = accessType, + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + Scope = { }, + }; + + // Act and Assert + _ = Assert.Throws(options.Validate); + } + + [Theory] + [MemberData(nameof(AccessTypes))] + public static void Validate_Throws_If_Scope_Does_Not_Contain_Scope_shop_r(EtsyAuthenticationAccessType accessType) + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + AccessType = accessType, + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + }; + options.Scope.Clear(); + options.Scope.Add(ClaimTypes.Email); + + // Act and Assert + _ = Assert.Throws(options.Validate); + } + + [Theory] + [MemberData(nameof(AccessTypes))] + public static void Validate_Throws_If_IncludeDetailedUserInfo_Is_True_But_Does_Not_Contain_Scope_email_r(EtsyAuthenticationAccessType accessType) + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + AccessType = accessType, + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + IncludeDetailedUserInfo = true, + }; + + // Not Adding email scope, shop scope is already added by default + + // Act and Assert + _ = Assert.Throws(options.Validate); + } + + [Theory] + [MemberData(nameof(AccessTypes))] + public static void Validate_Does_Not_Throw_When_IncludeDetailedUserInfo_Is_False_And_Contains_Scope_email_r(EtsyAuthenticationAccessType accessType) + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + AccessType = accessType, + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + IncludeDetailedUserInfo = false, + }; + + // Adding email scope + options.Scope.Add(ClaimTypes.Email); + + // Act (no Assert) + options.Validate(); + } + + [Theory] + [MemberData(nameof(AccessTypes))] + public static void Validate_Throws_If_CallbackPath_Is_Null(EtsyAuthenticationAccessType accessType) + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + AccessType = accessType, + CallbackPath = null, + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + }; + + // Act and Assert + _ = Assert.Throws(nameof(EtsyAuthenticationOptions.CallbackPath), options.Validate); + } +} diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs index 8243a8866..49a013890 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs @@ -1,14 +1,10 @@ -/* +/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers * for more information concerning the license and the contributors participating to this project. */ -using System.Security.Claims; using AspNet.Security.OAuth.Etsy; -using Microsoft.AspNetCore.Authentication; -using Xunit; -using Xunit.Abstractions; namespace AspNet.Security.OAuth.Providers.Tests.Etsy; @@ -27,7 +23,7 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu } [Theory] - [InlineData(ClaimTypes.NameIdentifier, "789012")] // shop_id and user_id is used as primary identifier! + [InlineData(ClaimTypes.NameIdentifier, "789012")] [InlineData(ClaimTypes.Email, "test@example.com")] [InlineData(ClaimTypes.GivenName, "Test")] [InlineData(ClaimTypes.Surname, "User")] @@ -39,4 +35,50 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu [InlineData("urn:etsy:image_url", "https://i.etsystatic.com/test/test_75x75.jpg")] public async Task Can_Sign_In_Using_Etsy(string claimType, string claimValue) => await AuthenticateUserAndAssertClaimValue(claimType, claimValue); + + [Fact] + public async Task Does_Not_Include_Detailed_Claims_When_IncludeDetailedUserInfo_Is_False() + { + // Arrange: disable detailed user info enrichment + void ConfigureServices(IServiceCollection services) => services.PostConfigureAll(o => o.IncludeDetailedUserInfo = false); + + using var server = CreateTestServer(ConfigureServices); + + // Act + var claims = await AuthenticateUserAsync(server); + + // Assert basic claims are present + claims.ShouldContainKey("urn:etsy:user_id"); + claims.ShouldContainKey("urn:etsy:shop_id"); + + // Detailed claims should be absent when flag is false + claims.Keys.ShouldNotContain(ClaimTypes.Email); + claims.Keys.ShouldNotContain(ClaimTypes.GivenName); + claims.Keys.ShouldNotContain(ClaimTypes.Surname); + claims.Keys.ShouldNotContain("urn:etsy:primary_email"); + claims.Keys.ShouldNotContain("urn:etsy:first_name"); + claims.Keys.ShouldNotContain("urn:etsy:last_name"); + claims.Keys.ShouldNotContain("urn:etsy:image_url"); + } + + [Fact] + public async Task Includes_Detailed_Claims_When_IncludeDetailedUserInfo_Is_True() + { + // Arrange: explicitly enable detailed user info enrichment (default may already be true, set explicitly for clarity) + void ConfigureServices(IServiceCollection services) => services.PostConfigureAll(o => o.IncludeDetailedUserInfo = true); + + using var server = CreateTestServer(ConfigureServices); + + // Act + var claims = await AuthenticateUserAsync(server); + + // Assert detailed claims are present + claims.ShouldContainKey(ClaimTypes.Email); + claims.ShouldContainKey(ClaimTypes.GivenName); + claims.ShouldContainKey(ClaimTypes.Surname); + claims.ShouldContainKey("urn:etsy:primary_email"); + claims.ShouldContainKey("urn:etsy:first_name"); + claims.ShouldContainKey("urn:etsy:last_name"); + claims.ShouldContainKey("urn:etsy:image_url"); + } } From 436441e02b6167d22417f24b0579fe88855440a4 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Thu, 6 Nov 2025 20:39:33 +0100 Subject: [PATCH 06/32] chore: updated and documented test data in `bundle.json` --- .../Etsy/bundle.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json index 79c73d7b1..44544e3a6 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json @@ -2,16 +2,15 @@ "$schema": "https://raw.githubusercontent.com/justeat/httpclient-interception/master/src/HttpClientInterception/Bundles/http-request-bundle-schema.json", "items": [ { - "comment": "Etsy OAuth 2.0 token exchange endpoint", + "comment": "Etsy OAuth 2.0 token exchange endpoint response - returns sample token values from Etsy API Docs", "uri": "https://api.etsy.com/v3/public/oauth/token", "method": "POST", "contentFormat": "json", "contentJson": { - "access_token": "secret-access-token", + "access_token": "12345678.12345678.O1zLuwveeKjpIqCQFfmR-PaMMpBmagH6DljRAkK9qt05OtRKiANJOyZlMx3WQ_o2FdComQGuoiAWy3dxyGI4Ke_76PR", "token_type": "Bearer", "expires_in": 3600, - "refresh_token": "secret-refresh-token", - "scope": "email_r shops_r" + "refresh_token": "12345678.JNGIJtvLmwfDMhlYoOJl8aLR1BWottyHC6yhNcET-eC7RogSR5e1GTIXGrgrelWZalvh3YvvyLfKYYqvymd-u37Sjtx" } }, { @@ -36,4 +35,4 @@ } } ] -} \ No newline at end of file +} From 78190177ba6fc1743ec8d7c747c5333988b4c0af Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Thu, 6 Nov 2025 23:08:37 +0100 Subject: [PATCH 07/32] chore: Rename Public to Personal Access Type, to match the Etsy Api naming for the app Access level --- .../EtsyAuthenticationAccessType.cs | 4 ++-- .../EtsyAuthenticationOptions.cs | 3 ++- .../Etsy/EtsyAuthenticationOptionsTests.cs | 14 +++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs index 5341140c4..645e7bc84 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs @@ -9,10 +9,10 @@ public enum EtsyAuthenticationAccessType /// /// Public client access type aka 'private usage access' in Etsy App Registration. /// - Public, + Personal, //// //// Confidential client access type aka 'commercial usage access' in Etsy App Registration. // TODO: Uncomment if someone can verify that commercial usage access supports confidential clients. //// - // Confidential + // Commercial } diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index d975bc546..11d528332 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -37,6 +37,7 @@ public EtsyAuthenticationOptions() // Map basic user claims ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "user_id"); + ClaimActions.MapJsonKey(Claims.UserId, "user_id"); ClaimActions.MapJsonKey(Claims.ShopId, "shop_id"); // Map detailed user claims for detailed user info /v3/application/users/{user_id} @@ -66,7 +67,7 @@ public override void Validate() // because the won't be validated if the ClientSecret validation fails. base.Validate(); } - catch (ArgumentException ex) when (ex.ParamName == nameof(ClientSecret) && AccessType == EtsyAuthenticationAccessType.Public) + catch (ArgumentException ex) when (ex.ParamName == nameof(ClientSecret) && AccessType == EtsyAuthenticationAccessType.Personal) { // No client secret is required for Etsy API, which uses Authorization Code Flow https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1 with: // See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues/610. diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs index fa00cb319..03ae0d1a7 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs @@ -10,14 +10,14 @@ public static class EtsyAuthenticationOptionsTests { public static TheoryData AccessTypes => new() { - { EtsyAuthenticationAccessType.Public }, // Private Etsy API access does not use client secret (aka 'shared secret' in Etsy App Registration) https://developers.etsy.com/documentation/essentials/authentication + { EtsyAuthenticationAccessType.Personal }, // Private Etsy API access does not use client secret (aka 'shared secret' in Etsy App Registration) https://developers.etsy.com/documentation/essentials/authentication - // { EtsyAuthenticationAccessType.Confidential } // TODO: Verify commercial access app registration Authentication does support confidential clients. Etsy docs do not indicate this to be used at all but support stated this would be required for token refresh + // { EtsyAuthenticationAccessType.Commercial } // TODO: Verify commercial access app registration Authentication does support confidential clients. Etsy docs do not indicate this to be used at all but support stated this would be required for token refresh }; [Theory] - [InlineData(null, EtsyAuthenticationAccessType.Public)] - [InlineData("", EtsyAuthenticationAccessType.Public)] + [InlineData(null, EtsyAuthenticationAccessType.Personal)] + [InlineData("", EtsyAuthenticationAccessType.Personal)] public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided_For_Public_Access_Type(string? clientSecret, EtsyAuthenticationAccessType accessType) { // Arrange @@ -49,9 +49,9 @@ public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided_For_P // _ = Assert.Throws("ClientSecret", options.Validate); // } [Theory] - [InlineData(EtsyAuthenticationAccessType.Public, true, false)] - [InlineData(EtsyAuthenticationAccessType.Public, false, false)] - [InlineData(EtsyAuthenticationAccessType.Public, false, true)] + [InlineData(EtsyAuthenticationAccessType.Personal, true, false)] + [InlineData(EtsyAuthenticationAccessType.Personal, false, false)] + [InlineData(EtsyAuthenticationAccessType.Personal, false, true)] public static void Validate_Throws_If_SaveTokens_Or_Pkce_Is_Disabled(EtsyAuthenticationAccessType accessType, bool usePkce, bool saveTokens) { // Arrange From 2c84ed859a5d62015348ef22ea90f4ba40a1fca6 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Thu, 6 Nov 2025 23:26:28 +0100 Subject: [PATCH 08/32] docs(EtsyProvider): Add Provider usage guide with samples and specifics to this API --- docs/etsy.md | 748 +++++++++++++-------------------------------------- 1 file changed, 183 insertions(+), 565 deletions(-) diff --git a/docs/etsy.md b/docs/etsy.md index d8dd50b82..6c2a57d03 100644 --- a/docs/etsy.md +++ b/docs/etsy.md @@ -1,124 +1,102 @@ # Integrating the Etsy Provider -## Example +Etsy's OAuth implementation uses Authorization Code with PKCE and issues refresh tokens. This provider enables PKCE by default and validates scopes to match Etsy's requirements. + +## Quick Links + +- Register your App at [Apps You've Made](https://www.etsy.com/developers/your-apps) on Etsy. +- Official Etsy Authentication API Documentation: [Etsy Developer Documentation](https://developers.etsy.com/documentation/essentials/authentication) +- Requesting a Refresh OAuth Token: [Etsy Refresh Token Guide](https://developers.etsy.com/documentation/essentials/authentication#requesting-a-refresh-oauth-token) +- Etsy API Reference: [Etsy API Reference](https://developers.etsy.com/documentation/reference) + +## Quick start + +Add the Etsy provider in your authentication configuration and request any additional scopes you need ("shops_r" is added by default): ```csharp services.AddAuthentication(options => /* Auth configuration */) - .AddEtsy(options => - { - options.ClientId = "my-etsy-api-key"; - // Note: ClientSecret is optional. PKCE is used for security. - }); + .AddEtsy(options => + { + options.ClientId = builder.Configuration["Etsy:ClientId"]!; + + // Optional: request additional scopes + options.Scope.Add(AspNet.Security.OAuth.Etsy.EtsyAuthenticationConstants.Scopes.ListingsRead); + + // Optional: fetch extended profile (requires email_r) + // options.IncludeDetailedUserInfo = true; + // options.Scope.Add(AspNet.Security.OAuth.Etsy.EtsyAuthenticationConstants.Scopes.EmailRead); + }); ``` -## Required Settings +## Required Additional Settings -| Property Name | Property Type | Description | Default Value | -|:--|:--|:--|:--| -| `ClientId` | `string` | Your Etsy App API Key keystring from [Your Apps](https://www.etsy.com/developers/your-apps) | | +_None._ + +> [!NOTE] +> +> - ClientSecret is optional for apps registered with Personal Access (public client); Etsy's flow uses Authorization Code with PKCE. +> - PKCE is required and is enabled by default. +> - The default callback path is `/signin-etsy`. +> - Etsy requires at least one scope; `shops_r` must always be included and is added by default. +> - To call the [`getUser` endpoint](https://developers.etsy.com/documentation/reference/#operation/getUser) or when `IncludeDetailedUserInfo` is enabled, add `email_r`. ## Optional Settings | Property Name | Property Type | Description | Default Value | |:--|:--|:--|:--| -| `Scope` | `ICollection` | A collection of requested scopes. See [Etsy Scopes](https://developers.etsy.com/documentation/essentials/authentication#scopes) for available scopes. | `["email_r", "shops_r"]` | -| `SaveTokens` | `bool` | Defines whether access and refresh tokens should be stored in the authentication cookie | `true` | +| `Scope` | `ICollection` | Scopes to request. At least one scope is required and `shops_r` must be included (it is added by default). Add `email_r` if you enable `IncludeDetailedUserInfo`. | `["shops_r"]` | +| `IncludeDetailedUserInfo` | `bool` | Makes a second API call to fetch extended profile data (requires `email_r`). | `false` | +| `AccessType` | `EtsyAuthenticationAccessType` | Apps registered as `Personal Access` don't require the client secret in [Authorization Code Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1). | `Personal` | +| `SaveTokens` | `bool` | Persists access/refresh tokens (required by Etsy and validated). | `true` | -## Getting Started +### Scope constants -To use the Etsy provider, you need to: +Use `EtsyAuthenticationConstants.Scopes.*` instead of string literals. Common values: -1. **Create an Etsy App** at [etsy.com/developers/your-apps](https://www.etsy.com/developers/your-apps) -2. **Configure your redirect URI** to match your callback path (default: `/signin-etsy`) -3. **Note your API Key and Secret** from the app settings +- `EmailRead` → `email_r` +- `ListingsRead` → `listings_r` +- `ListingsWrite` → `listings_w` +- `ShopsRead` → `shops_r` +- `TransactionsRead` → `transactions_r` -## Important Notes +## Validation behavior -- **PKCE Required**: Etsy requires PKCE (Proof Key for Code Exchange) for all OAuth flows. This is automatically enabled. -- **Client Secret Optional**: PKCE provides security without requiring a client secret. The provider works with only a Client ID. -- **Scopes Required**: Etsy requires at least one scope to be specified. The default scopes are `email_r` and `shops_r`. -- **Refresh Tokens**: Etsy provides refresh tokens with a 90-day lifetime, while access tokens expire after 1 hour. -- **User ID and Shop ID**: Etsy provides both a `user_id` and `shop_id`. The `shop_id` is used as the primary identifier (ClaimTypes.NameIdentifier) as it's required for most Etsy API operations. -- **Two-Step User Info**: The provider makes two API calls: - 1. `/v3/application/users/me` - Returns `user_id` and `shop_id` - 2. `/v3/application/users/{user_id}` - Returns detailed user profile information +- PKCE and token saving are required and enforced by the options validator. +- Validation fails if no scopes are requested or if `shops_r` is missing. +- If `IncludeDetailedUserInfo` is true, `email_r` must be present. -## Available Scopes +## Refreshing tokens -Some commonly used Etsy scopes include: +This provider saves tokens by default (`SaveTokens = true`). Etsy issues a refresh token; you are responsible for performing the refresh flow using the saved token when the access token expires. -- `email_r` - Read user profile and email address -- `listings_r` - Read user's listings -- `listings_w` - Create and edit listings -- `shops_r` - Read shop information -- `shops_w` - Update shop information -- `transactions_r` - Read transaction data -- `transactions_w` - Update transaction data -- `profile_r` - Read private profile information -- `profile_w` - Update profile information +```csharp +var refreshToken = await HttpContext.GetTokenAsync("refresh_token"); +``` -For a complete list, see the [Etsy OAuth Scopes documentation](https://developers.etsy.com/documentation/essentials/authentication#scopes). +See [Requesting a Refresh OAuth Token](#quick-links) in the Quick Links above for the HTTP details. ## Claims -The Etsy provider maps the following user profile fields to claims: +Basic claims are populated from `/v3/application/users/me`. When `IncludeDetailedUserInfo` is enabled and `email_r` is granted, additional claims are populated from `/v3/application/users/{user_id}`. | Claim Type | Value Source | Description | |:--|:--|:--| -| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | `shop_id` | The user's shop ID (primary identifier for Etsy API operations) | -| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | `primary_email` | The user's primary email address | -| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | `first_name` | The user's first name | -| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | `last_name` | The user's last name | -| `urn:etsy:user_id` | `user_id` | The user's unique ID (Etsy-specific claim) | -| `urn:etsy:shop_id` | `shop_id` | The user's shop ID (Etsy-specific claim) | -| `urn:etsy:primary_email` | `primary_email` | The user's primary email (Etsy-specific claim) | -| `urn:etsy:first_name` | `first_name` | The user's first name (Etsy-specific claim) | -| `urn:etsy:last_name` | `last_name` | The user's last name (Etsy-specific claim) | -| `urn:etsy:image_url` | `image_url_75x75` | URL to the user's 75x75 profile image | - -## Usage Example - -### Configure Authentication in Program.cs - -# [Controller-based](#tab/controller/setup) - -```csharp -using AspNet.Security.OAuth.Etsy; -using Microsoft.AspNetCore.Authentication.Cookies; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddAuthentication(options => -{ - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme; -}) -.AddCookie() -.AddEtsy(options => -{ - options.ClientId = builder.Configuration["Etsy:ClientId"]!; - - // Optional: Add additional scopes - options.Scope.Add("listings_r"); - options.Scope.Add("transactions_r"); - - // SaveTokens is true by default, allowing you to retrieve - // access_token and refresh_token later - options.SaveTokens = true; -}); - -builder.Services.AddControllersWithViews(); +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | `user_id` | Primary user identifier | +| `urn:etsy:user_id` | `user_id` | Etsy-specific user ID claim (in addition to NameIdentifier) | +| `urn:etsy:shop_id` | `shop_id` | User's shop ID | +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | `primary_email` | Primary email address | +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | `first_name` | First name | +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | `last_name` | Last name | +| `urn:etsy:primary_email` | `primary_email` | Etsy-specific email claim | +| `urn:etsy:first_name` | `first_name` | Etsy-specific first name claim | +| `urn:etsy:last_name` | `last_name` | Etsy-specific last name claim | +| `urn:etsy:image_url` | `image_url_75x75` | 75x75 profile image URL | -var app = builder.Build(); - -app.UseAuthentication(); -app.UseAuthorization(); -app.MapControllers(); +## Configuration -app.Run(); -``` +### Minimal configuration -# [Minimal API](#tab/minimal/setup) +#### [Program.cs](#tab/minimal-configuration-program) ```csharp using AspNet.Security.OAuth.Etsy; @@ -135,14 +113,12 @@ builder.Services.AddAuthentication(options => .AddEtsy(options => { options.ClientId = builder.Configuration["Etsy:ClientId"]!; - - // Optional: Add additional scopes - options.Scope.Add("listings_r"); - options.Scope.Add("transactions_r"); - - // SaveTokens is true by default, allowing you to retrieve - // access_token and refresh_token later - options.SaveTokens = true; + + // Enable extended profile (requires email_r) + // options.IncludeDetailedUserInfo = true; + // options.Scope.Add(EtsyAuthenticationConstants.Scopes.EmailRead); + + // Add other optional scopes (shops_r is added by default) }); var app = builder.Build(); @@ -150,207 +126,69 @@ var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); -// Map your minimal API endpoints -app.MapGet("/", () => "Hello World!"); - -// See individual endpoint examples below, or use the feature-based -// approach with app.MapEtsyAuth() shown at the bottom of this page - app.Run(); ``` ---- - -### Configuration with appsettings.json - -**How Configuration Works:** - -The `AddEtsy()` extension method accepts an `Action` delegate where you configure the provider. You can read values from configuration using `builder.Configuration["key"]` or bind entire sections. - -**Using Scope Constants:** - -Instead of magic strings, use the provided `EtsyAuthenticationConstants.Scopes` constants: - -```csharp -using AspNet.Security.OAuth.Etsy; - -.AddEtsy(options => -{ - options.ClientId = builder.Configuration["Etsy:ClientId"]!; - - // Use constants instead of magic strings - options.Scope.Add(EtsyAuthenticationConstants.Scopes.ListingsRead); - options.Scope.Add(EtsyAuthenticationConstants.Scopes.TransactionsRead); -}); -``` - -**Available Scope Constants:** - -| Constant | Scope Value | Description | -|:--|:--|:--| -| `Scopes.EmailRead` | `email_r` | Read user profile and email address | -| `Scopes.ListingsRead` | `listings_r` | Read user's listings | -| `Scopes.ListingsWrite` | `listings_w` | Create and edit listings | -| `Scopes.ListingsDelete` | `listings_d` | Delete listings | -| `Scopes.ShopsRead` | `shops_r` | Read shop information | -| `Scopes.ShopsWrite` | `shops_w` | Update shop information | -| `Scopes.ShopsDelete` | `shops_d` | Delete shop information | -| `Scopes.TransactionsRead` | `transactions_r` | Read transaction data | -| `Scopes.TransactionsWrite` | `transactions_w` | Update transaction data | -| `Scopes.BillingRead` | `billing_r` | Read billing information | -| `Scopes.ProfileRead` | `profile_r` | Read private profile information | -| `Scopes.ProfileWrite` | `profile_w` | Update profile information | -| `Scopes.AddressRead` | `address_r` | Read user's addresses | -| `Scopes.AddressWrite` | `address_w` | Write user's addresses | -| `Scopes.FavoritesRead` | `favorites_r` | Read user's favorites | -| `Scopes.FavoritesWrite` | `favorites_w` | Write user's favorites | -| `Scopes.FeedbackRead` | `feedback_r` | Read user's feedback | -| `Scopes.ShopsMyRead` | `shops_my_r` | Read user's shops | -| `Scopes.CartRead` | `cart_r` | Read user's cart | -| `Scopes.CartWrite` | `cart_w` | Write user's cart | -| `Scopes.RecommendRead` | `recommend_r` | Read user's recommendations | -| `Scopes.RecommendWrite` | `recommend_w` | Write user's recommendations | - -**Configuration Files:** - -The examples above use `builder.Configuration["Etsy:ClientId"]` to read from configuration. Here's how to set up your `appsettings.json`: - -**appsettings.json:** - -```json -{ - "Etsy": { - "ClientId": "your-etsy-api-key-here" - }, - "Logging": { - "LogLevel": { - "Default": "Information" - } - } -} -``` - -**appsettings.Development.json** (for local development): +#### [appsettings.json or appsettings.Development.json](#tab/minimal-configuration-appsettings) ```json { "Etsy": { - "ClientId": "your-development-api-key" + "ClientId": "your-etsy-api-key" } } ``` -**Using Environment Variables** (recommended for production): +*** -```bash -# Linux/macOS -export Etsy__ClientId="your-production-api-key" - -# Windows PowerShell -$env:Etsy__ClientId = "your-production-api-key" - -# Docker --e Etsy__ClientId="your-production-api-key" -``` - -**Using User Secrets** (for local development): - -```bash -dotnet user-secrets init -dotnet user-secrets set "Etsy:ClientId" "your-api-key" -``` - -**Advanced Configuration** (binding to options object): - -```csharp -// Create a configuration class -public class EtsySettings -{ - public string ClientId { get; set; } = string.Empty; - public List Scopes { get; set; } = new(); -} - -// In Program.cs -builder.Services.Configure( - builder.Configuration.GetSection("Etsy")); - -// Use it in authentication setup -builder.Services.AddEtsy(options => -{ - var etsySettings = builder.Configuration - .GetSection("Etsy") - .Get()!; - - options.ClientId = etsySettings.ClientId; - - foreach (var scope in etsySettings.Scopes) - { - options.Scope.Add(scope); - } -}); -``` +### Advanced using App Settings -**appsettings.json for advanced configuration:** +You can keep using code-based configuration, or bind from configuration values. Here is a comprehensive `appsettings.json` example covering supported options and common scopes: ```json { "Etsy": { "ClientId": "your-etsy-api-key", - "Scopes": [ - "email_r", - "shops_r", - "listings_r", - "transactions_r" - ] + "AccessType": "Personal", + "IncludeDetailedUserInfo": true, + "SaveTokens": true, + "Scopes": [ "shops_r", "email_r" ] + }, + "Logging": { + "LogLevel": { "Default": "Information" } } } ``` -### Accessing Claims - -# [Controller-based](#tab/controller/claims) +If you bind from configuration, set the options in code, for example: ```csharp -using AspNet.Security.OAuth.Etsy; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; - -[Authorize] -public class ProfileController : Controller +.AddEtsy(options => { - public IActionResult Index() + var section = builder.Configuration.GetSection("Etsy"); + options.ClientId = section["ClientId"]!; + options.AccessType = Enum.Parse(section["AccessType"] ?? "Personal", true); + options.IncludeDetailedUserInfo = bool.TryParse(section["IncludeDetailedUserInfo"], out var detailed) && detailed; + options.SaveTokens = !bool.TryParse(section["SaveTokens"], out var save) || save; // defaults to true + + // Apply scopes from config if present + var scopes = section.GetSection("Scopes").Get(); + if (scopes is { Length: > 0 }) { - // Get shop_id (primary identifier for Etsy API operations) - var shopId = User.FindFirstValue(ClaimTypes.NameIdentifier); - // OR - var shopIdAlt = User.FindFirstValue(EtsyAuthenticationConstants.Claims.ShopId); - - // Get user_id - var userId = User.FindFirstValue(EtsyAuthenticationConstants.Claims.UserId); - - // Get other user information - var email = User.FindFirstValue(ClaimTypes.Email); - var firstName = User.FindFirstValue(ClaimTypes.GivenName); - var lastName = User.FindFirstValue(ClaimTypes.Surname); - var imageUrl = User.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl); - - var model = new + foreach (var scope in scopes) { - ShopId = shopId, - UserId = userId, - Email = email, - FirstName = firstName, - LastName = lastName, - ImageUrl = imageUrl - }; - - return View(model); + options.Scope.Add(scope); + } } -} +}) ``` -# [Minimal API](#tab/minimal/claims) +> [!NOTE] +> Make sure to use proper [Secret Management for production applications](https://learn.microsoft.com/aspnet/core/security/app-secrets). + +## Accessing claims + +**Using Minimal API:** ```csharp using AspNet.Security.OAuth.Etsy; @@ -358,186 +196,18 @@ using System.Security.Claims; app.MapGet("/profile", (ClaimsPrincipal user) => { - // Get shop_id (primary identifier for Etsy API operations) - var shopId = user.FindFirstValue(ClaimTypes.NameIdentifier); - // OR - var shopIdAlt = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ShopId); - - // Get user_id - var userId = user.FindFirstValue(EtsyAuthenticationConstants.Claims.UserId); - - // Get other user information - var email = user.FindFirstValue(ClaimTypes.Email); - var firstName = user.FindFirstValue(ClaimTypes.GivenName); - var lastName = user.FindFirstValue(ClaimTypes.Surname); - var imageUrl = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl); - - return Results.Ok(new - { - ShopId = shopId, - UserId = userId, - Email = email, - FirstName = firstName, - LastName = lastName, - ImageUrl = imageUrl - }); + var userId = user.FindFirstValue(ClaimTypes.NameIdentifier); + var shopId = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ShopId); + var email = user.FindFirstValue(ClaimTypes.Email); + var firstName = user.FindFirstValue(ClaimTypes.GivenName); + var lastName = user.FindFirstValue(ClaimTypes.Surname); + var imageUrl = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl); + + return Results.Ok(new { userId, shopId, email, firstName, lastName, imageUrl }); }).RequireAuthorization(); ``` ---- - -### Retrieving Access and Refresh Tokens - -# [Controller-based](#tab/controller/tokens) - -```csharp -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -[Authorize] -public class TokenController : Controller -{ - public async Task GetTokens() - { - // Retrieve the stored tokens - var accessToken = await HttpContext.GetTokenAsync("access_token"); - var refreshToken = await HttpContext.GetTokenAsync("refresh_token"); - var expiresAt = await HttpContext.GetTokenAsync("expires_at"); - - return Ok(new - { - AccessToken = accessToken, - RefreshToken = refreshToken, - ExpiresAt = expiresAt - }); - } -} -``` - -# [Minimal API](#tab/minimal/tokens) - -```csharp -using Microsoft.AspNetCore.Authentication; - -app.MapGet("/tokens", async (HttpContext context) => -{ - // Retrieve the stored tokens - var accessToken = await context.GetTokenAsync("access_token"); - var refreshToken = await context.GetTokenAsync("refresh_token"); - var expiresAt = await context.GetTokenAsync("expires_at"); - - return Results.Ok(new - { - AccessToken = accessToken, - RefreshToken = refreshToken, - ExpiresAt = expiresAt - }); -}).RequireAuthorization(); -``` - ---- - -### Triggering Authentication - -# [Controller-based](#tab/controller/auth) - -```csharp -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; -using AspNet.Security.OAuth.Etsy; - -public class AccountController : Controller -{ - [HttpGet("~/signin")] - public IActionResult SignIn(string returnUrl = "/") - { - return Challenge(new AuthenticationProperties - { - RedirectUri = returnUrl - }, EtsyAuthenticationDefaults.AuthenticationScheme); - } - - [HttpGet("~/signout")] - public async Task SignOut() - { - await HttpContext.SignOutAsync(); - return RedirectToAction("Index", "Home"); - } -} -``` - -# [Minimal API](#tab/minimal/auth) - -```csharp -using Microsoft.AspNetCore.Authentication; -using AspNet.Security.OAuth.Etsy; - -app.MapGet("/signin", (string? returnUrl) => -{ - return Results.Challenge( - new AuthenticationProperties - { - RedirectUri = returnUrl ?? "/" - }, - new[] { EtsyAuthenticationDefaults.AuthenticationScheme }); -}); - -app.MapGet("/signout", async (HttpContext context) => -{ - await context.SignOutAsync(); - return Results.Redirect("/"); -}); -``` - ---- - -### Complete Minimal API Example with Feature-Based Structure - -This example demonstrates a modern, feature-based approach using extension methods and `MapGroup`. - -**How Authentication Connects:** - -1. **`AddAuthentication().AddEtsy()`** registers the Etsy OAuth handler with scheme `"Etsy"` -2. **`app.UseAuthentication()`** activates the authentication middleware -3. **`TypedResults.Challenge(..., authenticationSchemes)`** triggers the registered Etsy handler -4. **`.RequireAuthorization()`** uses the authenticated user from the handler -5. **`ClaimsPrincipal`** is automatically injected with claims from successful authentication - -**Program.cs:** - -```csharp -using AspNet.Security.OAuth.Etsy; -using Microsoft.AspNetCore.Authentication.Cookies; -using MyApi.Features.Authorization; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddAuthentication(options => -{ - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme; -}) -.AddCookie() -.AddEtsy(options => -{ - options.ClientId = builder.Configuration["Etsy:ClientId"]!; - options.Scope.Add(EtsyAuthenticationConstants.Scopes.ListingsRead); - options.Scope.Add(EtsyAuthenticationConstants.Scopes.TransactionsRead); -}); - -var app = builder.Build(); - -app.UseAuthentication(); -app.UseAuthorization(); - -// Map Etsy authentication endpoints -app.MapEtsyAuth(); - -app.Run(); -``` - -**Features/Authorization/EtsyAuthEndpoints.cs:** +## Feature-style typed Minimal API endpoints with MapGroup ```csharp using AspNet.Security.OAuth.Etsy; @@ -550,141 +220,89 @@ namespace MyApi.Features.Authorization; public static class EtsyAuthEndpoints { - public static IEndpointRouteBuilder MapEtsyAuth(this IEndpointRouteBuilder app) - { - var group = app.MapGroup("/etsy") - .WithTags("Etsy Authentication"); - - // Authentication endpoints - // When user navigates here, Challenge triggers the Etsy OAuth handler - group.MapGet("/signin", SignInAsync) - .WithName("EtsySignIn") - .WithSummary("Initiate Etsy OAuth authentication"); - - group.MapGet("/signout", SignOutAsync) - .WithName("EtsySignOut") - .WithSummary("Sign out from Etsy authentication"); - - // Protected endpoints requiring authorization - // .RequireAuthorization() checks if user is authenticated - // ClaimsPrincipal is automatically injected with claims from Etsy handler - group.MapGet("/profile", GetProfileAsync) - .RequireAuthorization() - .WithName("EtsyProfile") - .WithSummary("Get authenticated user's Etsy profile"); - - group.MapGet("/tokens", GetTokensAsync) - .RequireAuthorization() - .WithName("EtsyTokens") - .WithSummary("Get OAuth access and refresh tokens"); - - return app; - } + public static IEndpointRouteBuilder MapEtsyAuth(this IEndpointRouteBuilder app) + { + var group = app.MapGroup("/etsy") + .WithTags("Etsy Authentication"); + + // Sign-in: triggers the Etsy OAuth handler + group.MapGet("/signin", SignInAsync) + .WithName("EtsySignIn") + .WithSummary("Initiate Etsy OAuth authentication"); + + // Sign-out: removes the auth cookie/session + group.MapGet("/signout", SignOutAsync) + .WithName("EtsySignOut") + .WithSummary("Sign out from Etsy authentication"); + + // Protected: returns the authenticated user's profile + group.MapGet("/user-info", GetProfileAsync) + .RequireAuthorization() + .WithName("User Info") + .WithSummary("Get authenticated user's information"); + + // Protected: returns saved OAuth tokens + group.MapGet("/tokens", GetTokensAsync) + .RequireAuthorization() + .WithName("EtsyTokens") + .WithSummary("Get OAuth access and refresh tokens"); + + return app; + } - private static IResult SignInAsync(string? returnUrl) - { - // This triggers the EtsyAuthenticationHandler (registered via AddEtsy) - // The scheme name "Etsy" connects to the registered handler - return TypedResults.Challenge( - new AuthenticationProperties { RedirectUri = returnUrl ?? "/" }, - new[] { EtsyAuthenticationDefaults.AuthenticationScheme }); - } + private static Results SignInAsync(string? returnUrl) + => TypedResults.Challenge( + new AuthenticationProperties { RedirectUri = returnUrl ?? "/" }, + new[] { EtsyAuthenticationDefaults.AuthenticationScheme }); - private static async Task SignOutAsync(HttpContext context) - { - // Signs out from the cookie authentication (removes the session) - await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - return TypedResults.Redirect("/"); - } + private static async Task SignOutAsync(HttpContext context) + { + await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + return TypedResults.Redirect("/"); + } - private static Task> GetProfileAsync(ClaimsPrincipal user) + private static Task> GetProfileAsync(ClaimsPrincipal user) + { + var profile = new UserInfo { - // The ClaimsPrincipal 'user' parameter is automatically injected - // It contains claims populated by EtsyAuthenticationHandler - // from the Etsy API responses - var profile = new UserProfile - { - ShopId = user.FindFirstValue(ClaimTypes.NameIdentifier)!, - UserId = user.FindFirstValue(EtsyAuthenticationConstants.Claims.UserId)!, - Email = user.FindFirstValue(ClaimTypes.Email), - FirstName = user.FindFirstValue(ClaimTypes.GivenName), - LastName = user.FindFirstValue(ClaimTypes.Surname), - ImageUrl = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl) - }; - - return Task.FromResult(TypedResults.Ok(profile)); - } + UserId = user.FindFirstValue(ClaimTypes.NameIdentifier)!, + ShopId = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ShopId)!, + Email = user.FindFirstValue(ClaimTypes.Email), + FirstName = user.FindFirstValue(ClaimTypes.GivenName), + LastName = user.FindFirstValue(ClaimTypes.Surname), + ImageUrl = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl) + }; + + return Task.FromResult(TypedResults.Ok(profile)); + } - private static async Task> GetTokensAsync(HttpContext context) + private static async Task> GetTokensAsync(HttpContext context) + { + var tokenInfo = new TokenInfo { - // Tokens were saved by SaveTokens = true in AddEtsy options - // Retrieved from the authentication properties stored in the cookie - var tokenInfo = new TokenInfo - { - AccessToken = await context.GetTokenAsync("access_token"), - RefreshToken = await context.GetTokenAsync("refresh_token"), - ExpiresAt = await context.GetTokenAsync("expires_at") - }; + AccessToken = await context.GetTokenAsync("access_token"), + RefreshToken = await context.GetTokenAsync("refresh_token"), + ExpiresAt = await context.GetTokenAsync("expires_at") + }; - return TypedResults.Ok(tokenInfo); - } -} -``` - -**Features/Authorization/Models/UserProfile.cs:** - -```csharp -namespace MyApi.Features.Authorization; + return TypedResults.Ok(tokenInfo); + } -public record UserProfile -{ - public required string ShopId { get; init; } + public sealed record UserInfo + { public required string UserId { get; init; } + public required string ShopId { get; init; } public string? Email { get; init; } public string? FirstName { get; init; } public string? LastName { get; init; } public string? ImageUrl { get; init; } -} -``` - -**Features/Authorization/Models/TokenInfo.cs:** - -```csharp -namespace MyApi.Features.Authorization; + } -public record TokenInfo -{ + public sealed record TokenInfo + { public string? AccessToken { get; init; } public string? RefreshToken { get; init; } public string? ExpiresAt { get; init; } + } } ``` - -**Project Structure:** - -```text -MyApi/ -├── Program.cs -├── Features/ -│ └── Authorization/ -│ ├── EtsyAuthEndpoints.cs -│ └── Models/ -│ ├── UserProfile.cs -│ └── TokenInfo.cs -``` - -**Usage:** - -- Navigate to `/etsy/signin` to authenticate with Etsy -- After authentication, access `/etsy/profile` to see user information -- Access `/etsy/tokens` to retrieve OAuth tokens -- Navigate to `/etsy/signout` to sign out - -**Benefits of this approach:** - -- **Feature-based organization**: All Etsy authentication logic is contained in one feature folder -- **Clean Program.cs**: Single line `app.MapEtsyAuth()` keeps startup code minimal -- **Testability**: Extension method makes it easy to test endpoint registration -- **Discoverability**: Endpoints are documented with `WithName`, `WithSummary`, and `WithTags` -- **Extensibility**: Easy to add more Etsy-related endpoints to the same group -- **Type safety**: Uses `TypedResults` for strongly-typed responses From 87165d34f7e4bb432548ada76537f755b980f86f Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 13:02:05 +0100 Subject: [PATCH 09/32] chore: remove comments Co-authored-by: Martin Costello --- src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index 11d528332..e082e1ccb 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -26,10 +26,7 @@ public EtsyAuthenticationOptions() TokenEndpoint = EtsyAuthenticationDefaults.TokenEndpoint; UserInformationEndpoint = EtsyAuthenticationDefaults.UserInformationEndpoint; - // Enable PKCE by default (required by Etsy) UsePkce = true; - - // Enable refresh token support SaveTokens = true; // Default scopes - Etsy requires at least one scope and this is the one for basic user info From 80a6b9ad7b1b6a4cf58e82abd529ee3d64dd4b88 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 13:30:47 +0100 Subject: [PATCH 10/32] chore(EtsyProvider): tfm version bump Co-authored-by: Martin Costello --- .../AspNet.Security.OAuth.Etsy.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj b/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj index 105c515ab..c6b7b37ad 100644 --- a/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj +++ b/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj @@ -1,7 +1,10 @@ + 9.5.0 $(DefaultNetCoreTargetFramework) + + true From 66e76ab42bb0a9a6ad2baeab500eeb3c687ac7da Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 17:24:12 +0100 Subject: [PATCH 11/32] chore: applying PR rewording suggestion Co-authored-by: Martin Costello --- src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs index 898177289..887486d0b 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs @@ -40,7 +40,7 @@ protected override async Task CreateTicketAsync( if (!meResponse.IsSuccessStatusCode) { await Log.UserProfileErrorAsync(Logger, meResponse, Context.RequestAborted); - throw new HttpRequestException("An error occurred while retrieving basic user info from Etsy."); + throw new HttpRequestException("An error occurred while retrieving basic user information from Etsy."); } using var mePayload = JsonDocument.Parse(await meResponse.Content.ReadAsStringAsync(Context.RequestAborted)); From 9543daa17431c202c53fdf7cf09cdebee7f70509 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 14:26:09 +0100 Subject: [PATCH 12/32] chore(EtsyAccessTypes): Remove commented member and test/-cases that are not needed --- .../EtsyAuthenticationAccessType.cs | 7 +--- .../Etsy/EtsyAuthenticationOptionsTests.cs | 38 +++++-------------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs index 645e7bc84..a8133a4ef 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs @@ -7,12 +7,7 @@ namespace AspNet.Security.OAuth.Etsy; public enum EtsyAuthenticationAccessType { /// - /// Public client access type aka 'private usage access' in Etsy App Registration. + /// Public client access type aka Personal Access in Etsy App Registration. /// Personal, - - //// - //// Confidential client access type aka 'commercial usage access' in Etsy App Registration. // TODO: Uncomment if someone can verify that commercial usage access supports confidential clients. - //// - // Commercial } diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs index 03ae0d1a7..a0dd96131 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs @@ -10,20 +10,18 @@ public static class EtsyAuthenticationOptionsTests { public static TheoryData AccessTypes => new() { - { EtsyAuthenticationAccessType.Personal }, // Private Etsy API access does not use client secret (aka 'shared secret' in Etsy App Registration) https://developers.etsy.com/documentation/essentials/authentication - - // { EtsyAuthenticationAccessType.Commercial } // TODO: Verify commercial access app registration Authentication does support confidential clients. Etsy docs do not indicate this to be used at all but support stated this would be required for token refresh + { EtsyAuthenticationAccessType.Personal }, }; [Theory] - [InlineData(null, EtsyAuthenticationAccessType.Personal)] - [InlineData("", EtsyAuthenticationAccessType.Personal)] - public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided_For_Public_Access_Type(string? clientSecret, EtsyAuthenticationAccessType accessType) + [InlineData(null)] + [InlineData("")] + public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided_For_Personal_Access_Type(string? clientSecret) { // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = accessType, + AccessType = EtsyAuthenticationAccessType.Personal, ClientId = "my-client-id", ClientSecret = clientSecret!, }; @@ -32,32 +30,16 @@ public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided_For_P options.Validate(); } - // Leaving this test commented out to for case that ClientSecret is used with commercial access type - so this can be reactivated - // [Theory] - // [InlineData(EtsyAuthenticationAccessType.Commercial)] - // public static void Validate_Throws_If_ClientSecret_Is_Null(EtsyAuthenticationAccessType accessType) - // { - // // Arrange - // var options = new EtsyAuthenticationOptions() - // { - // AccessType = accessType, - // ClientId = "my-client-id", - // ClientSecret = null!, - // }; - // - // // Act and Assert - // _ = Assert.Throws("ClientSecret", options.Validate); - // } [Theory] - [InlineData(EtsyAuthenticationAccessType.Personal, true, false)] - [InlineData(EtsyAuthenticationAccessType.Personal, false, false)] - [InlineData(EtsyAuthenticationAccessType.Personal, false, true)] - public static void Validate_Throws_If_SaveTokens_Or_Pkce_Is_Disabled(EtsyAuthenticationAccessType accessType, bool usePkce, bool saveTokens) + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(false, true)] + public static void Validate_Throws_If_SaveTokens_Or_Pkce_Is_Disabled(bool usePkce, bool saveTokens) { // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = accessType, + AccessType = EtsyAuthenticationAccessType.Personal, ClientId = "my-client-id", ClientSecret = "my-client-secret", SaveTokens = saveTokens, From b6be3ce893e6db39ae3da148ec3680b1346139e9 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 16:18:43 +0100 Subject: [PATCH 13/32] chore: Add DetailedUserInfoClaimMappings and add xml docs --- .../EtsyAuthenticationConstants.cs | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs index cbec6b902..555b356ac 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs @@ -4,6 +4,8 @@ * for more information concerning the license and the contributors participating to this project. */ +using System.Security.Claims; + namespace AspNet.Security.OAuth.Etsy; /// @@ -11,16 +13,65 @@ namespace AspNet.Security.OAuth.Etsy; /// public static class EtsyAuthenticationConstants { + /// + /// Contains claim type constants specific to Etsy authentication. + /// public static class Claims { - public const string UserId = "urn:etsy:user_id"; + /// The claim type for the user's Etsy shop ID. public const string ShopId = "urn:etsy:shop_id"; - public const string PrimaryEmail = "urn:etsy:primary_email"; - public const string FirstName = "urn:etsy:first_name"; - public const string LastName = "urn:etsy:last_name"; + + /// The claim type for the user's profile image URL. public const string ImageUrl = "urn:etsy:image_url"; } + /// + /// Defines all available mappings between claim types and their corresponding Etsy API JSON keys for detailed user information. + /// These claims are available when calling the /v3/application/users/{user_id} endpoint, + /// which requires the email_r scope and option to be enabled. + /// + /// + /// + /// Users can selectively add the claims they need via ClaimActions.MapJsonKey() in their configuration: + /// + /// + /// builder.AddEtsy(options => { + /// options.IncludeDetailedUserInfo = true; + /// options.Scope.Add(EtsyAuthenticationConstants.Scopes.EmailRead); + /// + /// // Add only the claims you need using type-safe lookup: + /// options.ClaimActions.MapJsonKey(ClaimTypes.Email, + /// EtsyAuthenticationConstants.DetailedUserInfoClaimMappings[ClaimTypes.Email]); + /// options.ClaimActions.MapJsonKey(ClaimTypes.GivenName, + /// EtsyAuthenticationConstants.DetailedUserInfoClaimMappings[ClaimTypes.GivenName]); + /// }); + /// + /// + /// Or use the dictionary to add all of them: + /// + /// + /// foreach (var (claimType, jsonKey) in EtsyAuthenticationConstants.DetailedUserInfoClaimMappings) + /// { + /// options.ClaimActions.MapJsonKey(claimType, jsonKey); + /// } + /// + /// + public static readonly IReadOnlyDictionary DetailedUserInfoClaimMappings = + new Dictionary + { + [ClaimTypes.Email] = "primary_email", + [ClaimTypes.GivenName] = "first_name", + [ClaimTypes.Surname] = "last_name", + [Claims.ImageUrl] = "image_url_75x75", + }; + + /// + /// Contains OAuth scope constants for Etsy authentication. + /// + /// + /// For a complete list of Etsy OAuth scopes, see the + /// Etsy OAuth Scopes table. + /// public static class Scopes { /// Read user profile and email address From f561612bb8bdd85db71cfcb00ada86c7d5e06c5d Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 16:21:42 +0100 Subject: [PATCH 14/32] chore(Etsy): align oauth scopes with the docs table there is no endpoint this could get from and as its in the table format in etsy api, it gets also not inserted into the api spec .json for CLI tools like Kiota --- .../EtsyAuthenticationConstants.cs | 86 +++++++++---------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs index 555b356ac..9372bfc13 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs @@ -74,70 +74,64 @@ public static class Claims /// public static class Scopes { - /// Read user profile and email address - public const string EmailRead = "email_r"; - - /// Read user's listings - public const string ListingsRead = "listings_r"; - - /// Create and edit listings - public const string ListingsWrite = "listings_w"; - - /// Delete listings - public const string ListingsDelete = "listings_d"; - - /// Read shop information - public const string ShopsRead = "shops_r"; - - /// Update shop information - public const string ShopsWrite = "shops_w"; - - /// Delete shop information - public const string ShopsDelete = "shops_d"; - - /// Read transaction data - public const string TransactionsRead = "transactions_r"; + /// Read billing and shipping addresses. + public const string AddressRead = "address_r"; - /// Update transaction data - public const string TransactionsWrite = "transactions_w"; + /// Update billing and shipping addresses. + public const string AddressWrite = "address_w"; - /// Read billing information + /// Read all billing statement data. public const string BillingRead = "billing_r"; - /// Read private profile information - public const string ProfileRead = "profile_r"; - - /// Update profile information - public const string ProfileWrite = "profile_w"; + /// Read shopping carts. + public const string CartRead = "cart_r"; - /// Read user's addresses - public const string AddressRead = "address_r"; + /// Add and remove items from shopping carts. + public const string CartWrite = "cart_w"; - /// Write user's addresses - public const string AddressWrite = "address_w"; + /// Read user profile and email address. + public const string EmailRead = "email_r"; - /// Read user's favorites + /// Read private favorites. public const string FavoritesRead = "favorites_r"; - /// Write user's favorites + /// Add and remove favorites. public const string FavoritesWrite = "favorites_w"; - /// Read user's feedback + /// Read purchase information in feedback. public const string FeedbackRead = "feedback_r"; - /// Read user's shops - public const string ShopsMyRead = "shops_my_r"; + /// Delete listings. + public const string ListingsDelete = "listings_d"; - /// Read user's cart - public const string CartRead = "cart_r"; + /// Read all listings, including expired listings. + public const string ListingsRead = "listings_r"; - /// Write user's cart - public const string CartWrite = "cart_w"; + /// Create and edit listings. + public const string ListingsWrite = "listings_w"; + + /// Read all profile data. + public const string ProfileRead = "profile_r"; - /// Read user's recommendations + /// Update user profile, avatar, and related data. + public const string ProfileWrite = "profile_w"; + + /// Read recommended listings. public const string RecommendRead = "recommend_r"; - /// Write user's recommendations + /// Accept and reject recommended listings. public const string RecommendWrite = "recommend_w"; + + /// Read private shop information. + public const string ShopsRead = "shops_r"; + + /// Update shop information. + public const string ShopsWrite = "shops_w"; + + /// Read all checkout and payment data. + public const string TransactionsRead = "transactions_r"; + + /// Update receipts. + public const string TransactionsWrite = "transactions_w"; } } From a4154143ce84d1aa7ecf98e84427a25e96ebbf66 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 16:59:50 +0100 Subject: [PATCH 15/32] chore(EtsyAccessType): Remove AccessType --- .../EtsyAuthenticationAccessType.cs | 13 ------ .../EtsyAuthenticationOptions.cs | 12 ++++-- .../Etsy/EtsyAuthenticationOptionsTests.cs | 41 ++++++------------- 3 files changed, 21 insertions(+), 45 deletions(-) delete mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs deleted file mode 100644 index a8133a4ef..000000000 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationAccessType.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers -// for more information concerning the license and the contributors participating to this project. - -namespace AspNet.Security.OAuth.Etsy; - -public enum EtsyAuthenticationAccessType -{ - /// - /// Public client access type aka Personal Access in Etsy App Registration. - /// - Personal, -} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index e082e1ccb..531f63dbd 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -48,9 +48,15 @@ public EtsyAuthenticationOptions() } /// - /// Gets or sets the value for the Etsy client's access type. + /// Gets or sets a value indicating whether to fetch detailed user information + /// from the getUser endpoint. /// - public EtsyAuthenticationAccessType AccessType { get; set; } + /// + /// When enabled, requires the email_r scope to be added to the Scope collection. + /// Users must also configure which claims to map via ClaimActions.MapJsonKey(). + /// See for available claims. + /// + public bool IncludeDetailedUserInfo { get; set; } /// public override void Validate() @@ -64,7 +70,7 @@ public override void Validate() // because the won't be validated if the ClientSecret validation fails. base.Validate(); } - catch (ArgumentException ex) when (ex.ParamName == nameof(ClientSecret) && AccessType == EtsyAuthenticationAccessType.Personal) + catch (ArgumentException ex) when (ex.ParamName == nameof(ClientSecret)) { // No client secret is required for Etsy API, which uses Authorization Code Flow https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1 with: // See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues/610. diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs index a0dd96131..5c4478364 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs @@ -8,20 +8,14 @@ namespace AspNet.Security.OAuth.Etsy; public static class EtsyAuthenticationOptionsTests { - public static TheoryData AccessTypes => new() - { - { EtsyAuthenticationAccessType.Personal }, - }; - [Theory] [InlineData(null)] [InlineData("")] - public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided_For_Personal_Access_Type(string? clientSecret) + public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided(string? clientSecret) { // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = EtsyAuthenticationAccessType.Personal, ClientId = "my-client-id", ClientSecret = clientSecret!, }; @@ -39,7 +33,6 @@ public static void Validate_Throws_If_SaveTokens_Or_Pkce_Is_Disabled(bool usePkc // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = EtsyAuthenticationAccessType.Personal, ClientId = "my-client-id", ClientSecret = "my-client-secret", SaveTokens = saveTokens, @@ -50,14 +43,12 @@ public static void Validate_Throws_If_SaveTokens_Or_Pkce_Is_Disabled(bool usePkc _ = Assert.Throws(options.Validate); } - [Theory] - [MemberData(nameof(AccessTypes))] - public static void Validate_Throws_If_Scope_Is_Empty(EtsyAuthenticationAccessType accessType) + [Fact] + public static void Validate_Throws_If_Scope_Is_Empty() { // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = accessType, ClientId = "my-client-id", ClientSecret = "my-client-secret", Scope = { }, @@ -67,14 +58,12 @@ public static void Validate_Throws_If_Scope_Is_Empty(EtsyAuthenticationAccessTyp _ = Assert.Throws(options.Validate); } - [Theory] - [MemberData(nameof(AccessTypes))] - public static void Validate_Throws_If_Scope_Does_Not_Contain_Scope_shop_r(EtsyAuthenticationAccessType accessType) + [Fact] + public static void Validate_Throws_If_Scope_Does_Not_Contain_Scope_shop_r() { // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = accessType, ClientId = "my-client-id", ClientSecret = "my-client-secret", }; @@ -85,14 +74,12 @@ public static void Validate_Throws_If_Scope_Does_Not_Contain_Scope_shop_r(EtsyAu _ = Assert.Throws(options.Validate); } - [Theory] - [MemberData(nameof(AccessTypes))] - public static void Validate_Throws_If_IncludeDetailedUserInfo_Is_True_But_Does_Not_Contain_Scope_email_r(EtsyAuthenticationAccessType accessType) + [Fact] + public static void Validate_Throws_If_IncludeDetailedUserInfo_Is_True_But_Does_Not_Contain_Scope_email_r() { // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = accessType, ClientId = "my-client-id", ClientSecret = "my-client-secret", IncludeDetailedUserInfo = true, @@ -104,14 +91,12 @@ public static void Validate_Throws_If_IncludeDetailedUserInfo_Is_True_But_Does_N _ = Assert.Throws(options.Validate); } - [Theory] - [MemberData(nameof(AccessTypes))] - public static void Validate_Does_Not_Throw_When_IncludeDetailedUserInfo_Is_False_And_Contains_Scope_email_r(EtsyAuthenticationAccessType accessType) + [Fact] + public static void Validate_Does_Not_Throw_When_IncludeDetailedUserInfo_Is_False_And_Contains_Scope_email_r() { // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = accessType, ClientId = "my-client-id", ClientSecret = "my-client-secret", IncludeDetailedUserInfo = false, @@ -124,20 +109,18 @@ public static void Validate_Does_Not_Throw_When_IncludeDetailedUserInfo_Is_False options.Validate(); } - [Theory] - [MemberData(nameof(AccessTypes))] - public static void Validate_Throws_If_CallbackPath_Is_Null(EtsyAuthenticationAccessType accessType) + [Fact] + public static void Validate_Throws_If_CallbackPath_Is_Null() { // Arrange var options = new EtsyAuthenticationOptions() { - AccessType = accessType, CallbackPath = null, ClientId = "my-client-id", ClientSecret = "my-client-secret", }; // Act and Assert - _ = Assert.Throws(nameof(EtsyAuthenticationOptions.CallbackPath), options.Validate); + _ = Assert.Throws(nameof(EtsyAuthenticationOptions.CallbackPath), options.Validate); } } From ce0a1b253f7a0d2e035a23a44b95a34ea19180c9 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 18:16:28 +0100 Subject: [PATCH 16/32] chore(EtsyAuthenticationHandler): rename variables and formating apply CA1863 suggestions, apply xml docs changes --- .../EtsyAuthenticationDefaults.cs | 22 ++------ .../EtsyAuthenticationHandler.cs | 54 +++++++++++++------ .../EtsyAuthenticationOptions.cs | 14 +---- 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs index c098e2d19..9882b34bd 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs @@ -4,6 +4,8 @@ * for more information concerning the license and the contributors participating to this project. */ +using System.Text; + namespace AspNet.Security.OAuth.Etsy; /// @@ -29,9 +31,6 @@ public static class EtsyAuthenticationDefaults /// /// Default value for . /// - /// - /// "/signin-etsy" - /// public static readonly string CallbackPath = "/signin-etsy"; /// @@ -42,7 +41,7 @@ public static class EtsyAuthenticationDefaults /// /// Default value for . /// - public static readonly string TokenEndpoint = "https://api.etsy.com/v3/public/oauth/token"; + public static readonly string TokenEndpoint = "https://openapi.etsy.com/v3/public/oauth/token"; /// /// Default value for Etsy getMe Endpoint. @@ -50,18 +49,7 @@ public static class EtsyAuthenticationDefaults public static readonly string UserInformationEndpoint = "https://openapi.etsy.com/v3/application/users/me"; /// - /// Default value for the Etsy v3 API Base URI - /// - /// - /// https://openapi.etsy.com/v3/application/ - /// - public static readonly string EtsyBaseUri = "https://openapi.etsy.com/v3/application/"; - - /// - /// Default value for Etsy user details endpoint path getUser. + /// Default value for receiving the user profile based upon a unique user IDgetUser. /// - /// - /// "/users/{user_id}" - /// - public static readonly string UserDetailsPath = "/users/{user_id}"; + public static readonly CompositeFormat DetailedUserInfoEndpoint = CompositeFormat.Parse("https://openapi.etsy.com/v3/application/users/{0}"); } diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs index 887486d0b..ff3ee4331 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs @@ -31,20 +31,20 @@ protected override async Task CreateTicketAsync( [NotNull] OAuthTokenResponse tokens) { // Get the basic user info (user_id and shop_id) - using var meRequest = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint); - meRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); - meRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); - meRequest.Headers.Add("x-api-key", Options.ClientId); + using var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); + request.Headers.Add("x-api-key", Options.ClientId); - using var meResponse = await Backchannel.SendAsync(meRequest, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); - if (!meResponse.IsSuccessStatusCode) + using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); + if (!response.IsSuccessStatusCode) { - await Log.UserProfileErrorAsync(Logger, meResponse, Context.RequestAborted); + await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted); throw new HttpRequestException("An error occurred while retrieving basic user information from Etsy."); } - using var mePayload = JsonDocument.Parse(await meResponse.Content.ReadAsStringAsync(Context.RequestAborted)); - var meRoot = mePayload.RootElement; + using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted)); + var meRoot = payload.RootElement; // Extract user_id and shop_id from the /me response // Both fields should always be present in a successful Etsy OAuth response @@ -60,8 +60,26 @@ protected override async Task CreateTicketAsync( // Optionally enrich with detailed user info if (Options.IncludeDetailedUserInfo) { - using var detailedPayload = await GetDetailedUserInfoAsync(tokens); - context.RunClaimActions(detailedPayload.RootElement); + using var detailedPayload = await GetDetailedUserInfoAsync(tokens, userId); + var detailedRoot = detailedPayload.RootElement; + + // Apply claim actions for fields that are only in the detailed payload + // We filter the ClaimActions to exclude those for user_id and shop_id + // since they were already processed from the basic /users/me endpoint + foreach (var action in Options.ClaimActions) + { + // Skip the action if it's a JsonKeyClaimAction for user_id or shop_id + if (action is Microsoft.AspNetCore.Authentication.OAuth.Claims.JsonKeyClaimAction jsonAction) + { + if (jsonAction.ClaimType == ClaimTypes.NameIdentifier || + jsonAction.ClaimType == EtsyAuthenticationConstants.Claims.ShopId) + { + continue; + } + } + + action.Run(detailedRoot, identity, Options.ClaimsIssuer ?? ClaimsIssuer); + } } await Events.CreatingTicket(context); @@ -72,15 +90,17 @@ protected override async Task CreateTicketAsync( /// Retrieves detailed user information from Etsy. /// /// The OAuth token response. + /// The user ID to retrieve details for. /// A JSON document containing the detailed user information. - protected virtual async Task GetDetailedUserInfoAsync([NotNull] OAuthTokenResponse tokens) + protected virtual async Task GetDetailedUserInfoAsync([NotNull] OAuthTokenResponse tokens, long userId) { - using var userRequest = new HttpRequestMessage(HttpMethod.Get, EtsyAuthenticationDefaults.EtsyBaseUri + EtsyAuthenticationDefaults.UserDetailsPath); - userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); - userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); - userRequest.Headers.Add("x-api-key", Options.ClientId); + var userDetailsUrl = string.Format(null, EtsyAuthenticationDefaults.DetailedUserInfoEndpoint, userId); + using var request = new HttpRequestMessage(HttpMethod.Get, userDetailsUrl); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); + request.Headers.Add("x-api-key", Options.ClientId); - using var userResponse = await Backchannel.SendAsync(userRequest, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); + using var userResponse = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); if (!userResponse.IsSuccessStatusCode) { await Log.UserProfileErrorAsync(Logger, userResponse, Context.RequestAborted); diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index 531f63dbd..74c85415a 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -15,8 +15,6 @@ namespace AspNet.Security.OAuth.Etsy; /// public class EtsyAuthenticationOptions : OAuthOptions { - public bool IncludeDetailedUserInfo { get; set; } - public EtsyAuthenticationOptions() { ClaimsIssuer = EtsyAuthenticationDefaults.Issuer; @@ -34,17 +32,7 @@ public EtsyAuthenticationOptions() // Map basic user claims ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "user_id"); - ClaimActions.MapJsonKey(Claims.UserId, "user_id"); ClaimActions.MapJsonKey(Claims.ShopId, "shop_id"); - - // Map detailed user claims for detailed user info /v3/application/users/{user_id} - ClaimActions.MapJsonKey(ClaimTypes.Email, "primary_email"); - ClaimActions.MapJsonKey(ClaimTypes.GivenName, "first_name"); - ClaimActions.MapJsonKey(ClaimTypes.Surname, "last_name"); - ClaimActions.MapJsonKey(Claims.PrimaryEmail, "primary_email"); - ClaimActions.MapJsonKey(Claims.FirstName, "first_name"); - ClaimActions.MapJsonKey(Claims.LastName, "last_name"); - ClaimActions.MapJsonKey(Claims.ImageUrl, "image_url_75x75"); } /// @@ -54,7 +42,7 @@ public EtsyAuthenticationOptions() /// /// When enabled, requires the email_r scope to be added to the Scope collection. /// Users must also configure which claims to map via ClaimActions.MapJsonKey(). - /// See for available claims. + /// See for available claims. /// public bool IncludeDetailedUserInfo { get; set; } From 683a3c525fface077c2f003ce9ab653383804e97 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 21:56:02 +0100 Subject: [PATCH 17/32] chore(EtsyPostConfigureOptions): add DetailedUserInfo Config via PostConfiguration and add Extension for simpler ImageUriClaim --- .../ClaimActionCollectionExtensions.cs | 18 +++++ .../EtsyAuthenticationConstants.cs | 42 +--------- .../EtsyAuthenticationExtensions.cs | 3 + .../EtsyAuthenticationOptions.cs | 10 +-- .../EtsyPostConfigureOptions.cs | 32 ++++++++ .../Etsy/EtsyPostConfigureOptionsTests.cs | 76 +++++++++++++++++++ 6 files changed, 132 insertions(+), 49 deletions(-) create mode 100644 src/AspNet.Security.OAuth.Etsy/ClaimActionCollectionExtensions.cs create mode 100644 src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs create mode 100644 test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyPostConfigureOptionsTests.cs diff --git a/src/AspNet.Security.OAuth.Etsy/ClaimActionCollectionExtensions.cs b/src/AspNet.Security.OAuth.Etsy/ClaimActionCollectionExtensions.cs new file mode 100644 index 000000000..a9e5c8cc2 --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/ClaimActionCollectionExtensions.cs @@ -0,0 +1,18 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using AspNet.Security.OAuth.Etsy; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ClaimActionCollectionExtensions +{ + public static void MapImageClaim(this ClaimActionCollection collection) + { + collection.MapJsonKey(EtsyAuthenticationConstants.Claims.ImageUrl, "image_url_75x75"); + } +} diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs index 9372bfc13..efe5d092f 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs @@ -26,47 +26,7 @@ public static class Claims } /// - /// Defines all available mappings between claim types and their corresponding Etsy API JSON keys for detailed user information. - /// These claims are available when calling the /v3/application/users/{user_id} endpoint, - /// which requires the email_r scope and option to be enabled. - /// - /// - /// - /// Users can selectively add the claims they need via ClaimActions.MapJsonKey() in their configuration: - /// - /// - /// builder.AddEtsy(options => { - /// options.IncludeDetailedUserInfo = true; - /// options.Scope.Add(EtsyAuthenticationConstants.Scopes.EmailRead); - /// - /// // Add only the claims you need using type-safe lookup: - /// options.ClaimActions.MapJsonKey(ClaimTypes.Email, - /// EtsyAuthenticationConstants.DetailedUserInfoClaimMappings[ClaimTypes.Email]); - /// options.ClaimActions.MapJsonKey(ClaimTypes.GivenName, - /// EtsyAuthenticationConstants.DetailedUserInfoClaimMappings[ClaimTypes.GivenName]); - /// }); - /// - /// - /// Or use the dictionary to add all of them: - /// - /// - /// foreach (var (claimType, jsonKey) in EtsyAuthenticationConstants.DetailedUserInfoClaimMappings) - /// { - /// options.ClaimActions.MapJsonKey(claimType, jsonKey); - /// } - /// - /// - public static readonly IReadOnlyDictionary DetailedUserInfoClaimMappings = - new Dictionary - { - [ClaimTypes.Email] = "primary_email", - [ClaimTypes.GivenName] = "first_name", - [ClaimTypes.Surname] = "last_name", - [Claims.ImageUrl] = "image_url_75x75", - }; - - /// - /// Contains OAuth scope constants for Etsy authentication. + /// Contains the Etsy OAuth Scopes constants for Etsy authentication. /// /// /// For a complete list of Etsy OAuth scopes, see the diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs index 231205fdd..5b9831dd2 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationExtensions.cs @@ -71,6 +71,9 @@ public static AuthenticationBuilder AddEtsy( [CanBeNull] string caption, [NotNull] Action configuration) { + // Ensure Etsy-specific post-configuration runs after the base OAuth configuration + builder.Services.TryAddSingleton, EtsyPostConfigureOptions>(); + return builder.AddOAuth(scheme, caption, configuration); } } diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index 74c85415a..e41a3a5fd 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -27,23 +27,17 @@ public EtsyAuthenticationOptions() UsePkce = true; SaveTokens = true; - // Default scopes - Etsy requires at least one scope and this is the one for basic user info + // Etsy requires at least one scope and this is the one for basic user info Scope.Add(Scopes.ShopsRead); - // Map basic user claims ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "user_id"); ClaimActions.MapJsonKey(Claims.ShopId, "shop_id"); } /// /// Gets or sets a value indicating whether to fetch detailed user information - /// from the getUser endpoint. + /// from the getUser Endpoint. /// - /// - /// When enabled, requires the email_r scope to be added to the Scope collection. - /// Users must also configure which claims to map via ClaimActions.MapJsonKey(). - /// See for available claims. - /// public bool IncludeDetailedUserInfo { get; set; } /// diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs new file mode 100644 index 000000000..e48e8d6d8 --- /dev/null +++ b/src/AspNet.Security.OAuth.Etsy/EtsyPostConfigureOptions.cs @@ -0,0 +1,32 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using System.Security.Claims; +using Microsoft.Extensions.Options; + +namespace AspNet.Security.OAuth.Etsy; + +/// +/// Applies Etsy-specific post-configuration logic after user configuration and base OAuth setup. +/// +public sealed class EtsyPostConfigureOptions : IPostConfigureOptions +{ + public void PostConfigure(string? name, EtsyAuthenticationOptions options) + { + // Auto-add the email_r scope if detailed user info was requested but the scope not explicitly supplied. + if (options.IncludeDetailedUserInfo && !options.Scope.Contains(EtsyAuthenticationConstants.Scopes.EmailRead)) + { + options.Scope.Add(EtsyAuthenticationConstants.Scopes.EmailRead); + + options.ClaimActions.MapJsonKey(ClaimTypes.Email, "primary_email"); + options.ClaimActions.MapJsonKey(ClaimTypes.GivenName, "first_name"); + options.ClaimActions.MapJsonKey(ClaimTypes.Surname, "last_name"); + } + + // NOTE: We intentionally DO NOT auto-map the image to reduce data bloat, + // as the image data can be quite large and is not always needed. + } +} diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyPostConfigureOptionsTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyPostConfigureOptionsTests.cs new file mode 100644 index 000000000..1af573cf1 --- /dev/null +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyPostConfigureOptionsTests.cs @@ -0,0 +1,76 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +namespace AspNet.Security.OAuth.Etsy; + +public static class EtsyPostConfigureOptionsTests +{ + [Fact] + public static void PostConfigure_Adds_EmailRead_Scope_When_DetailedUserInfo_Enabled_And_Not_Contains_Scope_email_r() + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + IncludeDetailedUserInfo = true, + }; + + // Ensure email_r not already present + options.Scope.Remove(EtsyAuthenticationConstants.Scopes.EmailRead); + + var postConfigure = new EtsyPostConfigureOptions(); + + // Act + postConfigure.PostConfigure(EtsyAuthenticationDefaults.AuthenticationScheme, options); + + // Assert + options.Scope.ShouldContain(EtsyAuthenticationConstants.Scopes.EmailRead); + } + + [Fact] + public static void PostConfigure_Does_Not_Add_EmailRead_When_DetailedUserInfo_Disabled() + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + IncludeDetailedUserInfo = false, + }; + + var postConfigure = new EtsyPostConfigureOptions(); + + // Act + postConfigure.PostConfigure(EtsyAuthenticationDefaults.AuthenticationScheme, options); + + // Assert + options.Scope.ShouldNotContain(EtsyAuthenticationConstants.Scopes.EmailRead); + } + + [Fact] + public static void PostConfigure_Does_Not_Duplicate_EmailRead_Scope() + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + IncludeDetailedUserInfo = true, + }; + + // Add the email scope manually + options.Scope.Add(EtsyAuthenticationConstants.Scopes.EmailRead); + + var postConfigure = new EtsyPostConfigureOptions(); + + // Act + postConfigure.PostConfigure(EtsyAuthenticationDefaults.AuthenticationScheme, options); + + // Assert (will throw if duplicate exists) + options.Scope.ShouldBeUnique(); + } +} From ba8a05d429c79b968de122412121bd0a9a7c2151 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 21:57:12 +0100 Subject: [PATCH 18/32] chore: update const string to static readonly string --- .../EtsyAuthenticationConstants.cs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs index efe5d092f..054cdd330 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationConstants.cs @@ -19,79 +19,75 @@ public static class EtsyAuthenticationConstants public static class Claims { /// The claim type for the user's Etsy shop ID. - public const string ShopId = "urn:etsy:shop_id"; + public static readonly string ShopId = "urn:etsy:shop_id"; /// The claim type for the user's profile image URL. - public const string ImageUrl = "urn:etsy:image_url"; + public static readonly string ImageUrl = "urn:etsy:image_url"; } /// /// Contains the Etsy OAuth Scopes constants for Etsy authentication. /// - /// - /// For a complete list of Etsy OAuth scopes, see the - /// Etsy OAuth Scopes table. - /// public static class Scopes { /// Read billing and shipping addresses. - public const string AddressRead = "address_r"; + public static readonly string AddressRead = "address_r"; /// Update billing and shipping addresses. - public const string AddressWrite = "address_w"; + public static readonly string AddressWrite = "address_w"; /// Read all billing statement data. - public const string BillingRead = "billing_r"; + public static readonly string BillingRead = "billing_r"; /// Read shopping carts. - public const string CartRead = "cart_r"; + public static readonly string CartRead = "cart_r"; /// Add and remove items from shopping carts. - public const string CartWrite = "cart_w"; + public static readonly string CartWrite = "cart_w"; /// Read user profile and email address. - public const string EmailRead = "email_r"; + public static readonly string EmailRead = "email_r"; /// Read private favorites. - public const string FavoritesRead = "favorites_r"; + public static readonly string FavoritesRead = "favorites_r"; /// Add and remove favorites. - public const string FavoritesWrite = "favorites_w"; + public static readonly string FavoritesWrite = "favorites_w"; /// Read purchase information in feedback. - public const string FeedbackRead = "feedback_r"; + public static readonly string FeedbackRead = "feedback_r"; /// Delete listings. - public const string ListingsDelete = "listings_d"; + public static readonly string ListingsDelete = "listings_d"; /// Read all listings, including expired listings. - public const string ListingsRead = "listings_r"; + public static readonly string ListingsRead = "listings_r"; /// Create and edit listings. - public const string ListingsWrite = "listings_w"; + public static readonly string ListingsWrite = "listings_w"; /// Read all profile data. - public const string ProfileRead = "profile_r"; + public static readonly string ProfileRead = "profile_r"; /// Update user profile, avatar, and related data. - public const string ProfileWrite = "profile_w"; + public static readonly string ProfileWrite = "profile_w"; /// Read recommended listings. - public const string RecommendRead = "recommend_r"; + public static readonly string RecommendRead = "recommend_r"; /// Accept and reject recommended listings. - public const string RecommendWrite = "recommend_w"; + public static readonly string RecommendWrite = "recommend_w"; /// Read private shop information. - public const string ShopsRead = "shops_r"; + public static readonly string ShopsRead = "shops_r"; /// Update shop information. - public const string ShopsWrite = "shops_w"; + public static readonly string ShopsWrite = "shops_w"; /// Read all checkout and payment data. - public const string TransactionsRead = "transactions_r"; + public static readonly string TransactionsRead = "transactions_r"; /// Update receipts. - public const string TransactionsWrite = "transactions_w"; + public static readonly string TransactionsWrite = "transactions_w"; } } From f287b46a56809b60d010b17a83c81b30c68ddc75 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 22:03:08 +0100 Subject: [PATCH 19/32] chore: Update xml docs and refactor to Property pattern with declaration pattern --- .../EtsyAuthenticationHandler.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs index ff3ee4331..984321572 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs @@ -25,6 +25,14 @@ public EtsyAuthenticationHandler( { } + /// + /// Creates an from the OAuth tokens and Etsy user information. + /// + /// The claims identity to populate. + /// The authentication properties. + /// The OAuth token response containing the access token. + /// An containing the user claims and properties. + /// Thrown when an error occurs while retrieving user information from Etsy. protected override async Task CreateTicketAsync( [NotNull] ClaimsIdentity identity, [NotNull] AuthenticationProperties properties, @@ -49,7 +57,6 @@ protected override async Task CreateTicketAsync( // Extract user_id and shop_id from the /me response // Both fields should always be present in a successful Etsy OAuth response var userId = meRoot.GetProperty("user_id").GetInt64(); - var shopId = meRoot.GetProperty("shop_id").GetInt64(); var principal = new ClaimsPrincipal(identity); var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, meRoot); @@ -69,13 +76,11 @@ protected override async Task CreateTicketAsync( foreach (var action in Options.ClaimActions) { // Skip the action if it's a JsonKeyClaimAction for user_id or shop_id - if (action is Microsoft.AspNetCore.Authentication.OAuth.Claims.JsonKeyClaimAction jsonAction) + if (action is Microsoft.AspNetCore.Authentication.OAuth.Claims.JsonKeyClaimAction { ClaimType: var t } && + (t == ClaimTypes.NameIdentifier + || t == EtsyAuthenticationConstants.Claims.ShopId)) { - if (jsonAction.ClaimType == ClaimTypes.NameIdentifier || - jsonAction.ClaimType == EtsyAuthenticationConstants.Claims.ShopId) - { - continue; - } + continue; } action.Run(detailedRoot, identity, Options.ClaimsIssuer ?? ClaimsIssuer); @@ -91,7 +96,7 @@ protected override async Task CreateTicketAsync( /// /// The OAuth token response. /// The user ID to retrieve details for. - /// A JSON document containing the detailed user information. + /// A containing the detailed user information. protected virtual async Task GetDetailedUserInfoAsync([NotNull] OAuthTokenResponse tokens, long userId) { var userDetailsUrl = string.Format(null, EtsyAuthenticationDefaults.DetailedUserInfoEndpoint, userId); From 0596516d0292db4bd02138a82369dc01a5d9c1e5 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 22:05:47 +0100 Subject: [PATCH 20/32] chore(EtsyOptionsValidation): apply Review suggestions --- .../EtsyAuthenticationOptions.cs | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index e41a3a5fd..87210929a 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -58,53 +58,36 @@ public override void Validate() // See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues/610. } - // Ensure PKCE is enabled (required by Etsy) - if (!UsePkce) - { - throw new ArgumentException("PKCE is required by Etsy Authentication and must be enabled.", nameof(UsePkce)); - } - - if (!SaveTokens) - { - throw new ArgumentException("Saving tokens is required by Etsy Authentication and must be enabled.", nameof(SaveTokens)); - } - if (string.IsNullOrEmpty(AuthorizationEndpoint)) { - throw new ArgumentNullException($"The '{nameof(AuthorizationEndpoint)}' option must be provided.", nameof(AuthorizationEndpoint)); + throw new ArgumentNullException(nameof(AuthorizationEndpoint), $"The '{nameof(AuthorizationEndpoint)}' option must be provided."); } if (string.IsNullOrEmpty(TokenEndpoint)) { - throw new ArgumentNullException($"The '{nameof(TokenEndpoint)}' option must be provided.", nameof(TokenEndpoint)); + throw new ArgumentNullException(nameof(TokenEndpoint), $"The '{nameof(TokenEndpoint)}' option must be provided."); } if (string.IsNullOrEmpty(UserInformationEndpoint)) { - throw new ArgumentNullException($"The '{nameof(UserInformationEndpoint)}' option must be provided.", nameof(UserInformationEndpoint)); - } - - // Ensure at least one scope is requested (required by Etsy) - if (Scope.Count == 0) - { - throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), "At least one scope must be specified for Etsy authentication."); + throw new ArgumentNullException(nameof(UserInformationEndpoint), $"The '{nameof(UserInformationEndpoint)}' option must be provided."); } if (!Scope.Contains(Scopes.ShopsRead)) { - // ShopsRead scope is required to access basic user info - throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), $"The '{Scopes.ShopsRead}' scope must be specified for Etsy authentication UserInfoEndpoint: https://developers.etsy.com/documentation/reference#operation/getMe"); + // shops_r scope is required to access basic user info. + throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), $"The '{Scopes.ShopsRead}' scope must be specified."); } if (IncludeDetailedUserInfo && !Scope.Contains(Scopes.EmailRead)) { // EmailRead scope is required to access detailed user info - throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), $"The '{Scopes.EmailRead}' scope must be specified for Etsy authentication when '{nameof(IncludeDetailedUserInfo)}' is enabled."); + throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), $"The '{Scopes.EmailRead}' scope must be specified when '{nameof(IncludeDetailedUserInfo)}' is enabled."); } if (!CallbackPath.HasValue) { - throw new ArgumentException($"The '{nameof(CallbackPath)}' option must be provided.", nameof(CallbackPath)); + throw new ArgumentNullException(nameof(CallbackPath), $"The '{nameof(CallbackPath)}' option must be provided."); } } } From 8ecc5922e81a56d914e654ac313e87ca993b7ff2 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 22:09:20 +0100 Subject: [PATCH 21/32] test(EtsyProvider): Update tests accordingly to review suggestions and code changes in Etsy Provider --- .../Etsy/EtsyAuthenticationOptionsTests.cs | 55 +++++++++---------- .../Etsy/EtsyTests.cs | 38 ++++++------- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs index 5c4478364..40235dd15 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs @@ -24,89 +24,83 @@ public static void Validate_Does_Not_Throw_If_ClientSecret_Is_Not_Provided(strin options.Validate(); } - [Theory] - [InlineData(true, false)] - [InlineData(false, false)] - [InlineData(false, true)] - public static void Validate_Throws_If_SaveTokens_Or_Pkce_Is_Disabled(bool usePkce, bool saveTokens) + [Fact] + public static void Validate_Does_Throw_If_Scope_Does_Not_Contain_Scope_shop_r() { // Arrange var options = new EtsyAuthenticationOptions() { ClientId = "my-client-id", ClientSecret = "my-client-secret", - SaveTokens = saveTokens, - UsePkce = usePkce, }; + options.Scope.Clear(); + options.Scope.Add(EtsyAuthenticationConstants.Scopes.EmailRead); - // Act and Assert - _ = Assert.Throws(options.Validate); + // Act + _ = Assert.Throws(options.Validate); } [Fact] - public static void Validate_Throws_If_Scope_Is_Empty() + public static void Validate_Does_Not_Throw_When_IncludeDetailedUserInfo_Is_False_And_Contains_Scope_email_r() { // Arrange var options = new EtsyAuthenticationOptions() { ClientId = "my-client-id", ClientSecret = "my-client-secret", - Scope = { }, + IncludeDetailedUserInfo = false, }; - // Act and Assert - _ = Assert.Throws(options.Validate); + // Adding email scope should be harmless when IncludeDetailedUserInfo is false + options.Scope.Add(EtsyAuthenticationConstants.Scopes.EmailRead); + + // Act (no Assert) + options.Validate(); } [Fact] - public static void Validate_Throws_If_Scope_Does_Not_Contain_Scope_shop_r() + public static void Validate_Throws_If_AuthorizationEndpoint_Is_Null() { // Arrange var options = new EtsyAuthenticationOptions() { + AuthorizationEndpoint = null!, ClientId = "my-client-id", ClientSecret = "my-client-secret", }; - options.Scope.Clear(); - options.Scope.Add(ClaimTypes.Email); // Act and Assert - _ = Assert.Throws(options.Validate); + _ = Assert.Throws(nameof(options.AuthorizationEndpoint), options.Validate); } [Fact] - public static void Validate_Throws_If_IncludeDetailedUserInfo_Is_True_But_Does_Not_Contain_Scope_email_r() + public static void Validate_Throws_If_TokenEndpoint_Is_Null() { // Arrange var options = new EtsyAuthenticationOptions() { ClientId = "my-client-id", ClientSecret = "my-client-secret", - IncludeDetailedUserInfo = true, + TokenEndpoint = null!, }; - // Not Adding email scope, shop scope is already added by default - // Act and Assert - _ = Assert.Throws(options.Validate); + _ = Assert.Throws(nameof(options.TokenEndpoint), options.Validate); } [Fact] - public static void Validate_Does_Not_Throw_When_IncludeDetailedUserInfo_Is_False_And_Contains_Scope_email_r() + public static void Validate_Throws_If_UserInformationEndpoint_Is_Null() { // Arrange var options = new EtsyAuthenticationOptions() { ClientId = "my-client-id", ClientSecret = "my-client-secret", - IncludeDetailedUserInfo = false, + TokenEndpoint = null!, }; - // Adding email scope - options.Scope.Add(ClaimTypes.Email); - - // Act (no Assert) - options.Validate(); + // Act and Assert + _ = Assert.Throws(nameof(options.UserInformationEndpoint), options.Validate); } [Fact] @@ -121,6 +115,7 @@ public static void Validate_Throws_If_CallbackPath_Is_Null() }; // Act and Assert - _ = Assert.Throws(nameof(EtsyAuthenticationOptions.CallbackPath), options.Validate); + var ex = Assert.Throws(options.Validate); + ex.ParamName.ShouldBe(nameof(options.CallbackPath)); } } diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs index 49a013890..5855db247 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs @@ -5,6 +5,7 @@ */ using AspNet.Security.OAuth.Etsy; +using static AspNet.Security.OAuth.Etsy.EtsyAuthenticationConstants; namespace AspNet.Security.OAuth.Providers.Tests.Etsy; @@ -23,16 +24,8 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu } [Theory] - [InlineData(ClaimTypes.NameIdentifier, "789012")] - [InlineData(ClaimTypes.Email, "test@example.com")] - [InlineData(ClaimTypes.GivenName, "Test")] - [InlineData(ClaimTypes.Surname, "User")] - [InlineData("urn:etsy:user_id", "123456")] - [InlineData("urn:etsy:shop_id", "789012")] - [InlineData("urn:etsy:primary_email", "test@example.com")] - [InlineData("urn:etsy:first_name", "Test")] - [InlineData("urn:etsy:last_name", "User")] - [InlineData("urn:etsy:image_url", "https://i.etsystatic.com/test/test_75x75.jpg")] + [InlineData(ClaimTypes.NameIdentifier, "123456")] + [InlineData("shop_id", "789012")] public async Task Can_Sign_In_Using_Etsy(string claimType, string claimValue) => await AuthenticateUserAndAssertClaimValue(claimType, claimValue); @@ -48,24 +41,28 @@ public async Task Does_Not_Include_Detailed_Claims_When_IncludeDetailedUserInfo_ var claims = await AuthenticateUserAsync(server); // Assert basic claims are present - claims.ShouldContainKey("urn:etsy:user_id"); - claims.ShouldContainKey("urn:etsy:shop_id"); + claims.ShouldContainKey(ClaimTypes.NameIdentifier); + claims.ShouldContainKey(Claims.ShopId); // Detailed claims should be absent when flag is false claims.Keys.ShouldNotContain(ClaimTypes.Email); claims.Keys.ShouldNotContain(ClaimTypes.GivenName); claims.Keys.ShouldNotContain(ClaimTypes.Surname); - claims.Keys.ShouldNotContain("urn:etsy:primary_email"); - claims.Keys.ShouldNotContain("urn:etsy:first_name"); - claims.Keys.ShouldNotContain("urn:etsy:last_name"); - claims.Keys.ShouldNotContain("urn:etsy:image_url"); + claims.Keys.ShouldNotContain(Claims.ImageUrl); } [Fact] public async Task Includes_Detailed_Claims_When_IncludeDetailedUserInfo_Is_True() { - // Arrange: explicitly enable detailed user info enrichment (default may already be true, set explicitly for clarity) - void ConfigureServices(IServiceCollection services) => services.PostConfigureAll(o => o.IncludeDetailedUserInfo = true); + // Arrange: enable detailed user info, configure claims to map. + // Note: email_r will be auto-added by the provider's post-configure step. + void ConfigureServices(IServiceCollection services) => services.PostConfigureAll(o => + { + o.IncludeDetailedUserInfo = true; + + // User to include image claim + o.ClaimActions.MapImageClaim(); + }); using var server = CreateTestServer(ConfigureServices); @@ -76,9 +73,6 @@ public async Task Includes_Detailed_Claims_When_IncludeDetailedUserInfo_Is_True( claims.ShouldContainKey(ClaimTypes.Email); claims.ShouldContainKey(ClaimTypes.GivenName); claims.ShouldContainKey(ClaimTypes.Surname); - claims.ShouldContainKey("urn:etsy:primary_email"); - claims.ShouldContainKey("urn:etsy:first_name"); - claims.ShouldContainKey("urn:etsy:last_name"); - claims.ShouldContainKey("urn:etsy:image_url"); + claims.ShouldContainKey(Claims.ImageUrl); } } From 505722d06ed7227c6efa6545322dc8bb77d3d9d8 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 23:34:42 +0100 Subject: [PATCH 22/32] docs(EtsyProvider): Add links to etsy provider docs and author, update docs --- README.md | 2 + docs/README.md | 1 + docs/assets/Etsy-find-your-client_id.png | Bin 0 -> 15779 bytes docs/etsy.md | 272 +++++++++++------- .../AspNet.Security.OAuth.Etsy.csproj | 2 +- 5 files changed, 176 insertions(+), 101 deletions(-) create mode 100644 docs/assets/Etsy-find-your-client_id.png diff --git a/README.md b/README.md index 064ebc419..0367341ee 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ We would love it if you could help contributing to this repository. * [Sinan](https://github.com/SH2015) * [Stefan](https://github.com/Schlurcher) * [Steffen Wenz](https://github.com/swenz) +* [Sonja Schweitzer](https://github.com/DevTKSS) * [Tathagata Chakraborty](https://github.com/tatx) * [TheUltimateC0der](https://github.com/TheUltimateC0der) * [Tolbxela](https://github.com/tolbxela) @@ -182,6 +183,7 @@ If a provider you're looking for does not exist, consider making a PR to add one | Docusign | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Docusign?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Docusign/ "Download AspNet.Security.OAuth.Docusign from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Docusign?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Docusign "Download AspNet.Security.OAuth.Docusign from MyGet.org") | [Documentation](https://developers.docusign.com/platform/auth/ "Docusign developer documentation") | | Dropbox | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Dropbox?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Dropbox/ "Download AspNet.Security.OAuth.Dropbox from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Dropbox?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Dropbox "Download AspNet.Security.OAuth.Dropbox from MyGet.org") | [Documentation](https://www.dropbox.com/developers/reference/oauth-guide?_tk=guides_lp&_ad=deepdive2&_camp=oauth "Dropbox developer documentation") | | eBay | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Ebay?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Ebay/ "Download AspNet.Security.OAuth.Ebay from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Ebay?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Ebay "Download AspNet.Security.OAuth.Ebay from MyGet.org") | [Documentation](https://developer.ebay.com/api-docs/static/oauth-tokens.html "eBay developer documentation") | +| Etsy | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Etsy?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Etsy/ "Download AspNet.Security.OAuth.Etsy from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Etsy?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Etsy "Download AspNet.Security.OAuth.Etsy from MyGet.org") | [Documentation](https://developers.etsy.com/documentation/essentials/authentication "Etsy developer documentation") | | EVEOnline | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.EVEOnline?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.EVEOnline/ "Download AspNet.Security.OAuth.EVEOnline from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.EVEOnline?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.EVEOnline "Download AspNet.Security.OAuth.EVEOnline from MyGet.org") | [Documentation](https://github.com/esi/esi-docs/blob/master/docs/sso/web_based_sso_flow.md "EVEOnline developer documentation") | | ExactOnline | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.ExactOnline?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.ExactOnline/ "Download AspNet.Security.OAuth.ExactOnline from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.ExactOnline?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.ExactOnline "Download AspNet.Security.OAuth.ExactOnline from MyGet.org") | [Documentation](https://support.exactonline.com/community/s/knowledge-base#All-All-DNO-Content-gettingstarted "ExactOnline developer documentation") | | Feishu | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Feishu?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Feishu/ "Download AspNet.Security.OAuth.Feishu from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Feishu?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Feishu "Download AspNet.Security.OAuth.Feishu from MyGet.org") | [Documentation](https://open.feishu.cn/document/common-capabilities/sso/web-application-sso/web-app-overview "Feishu developer documentation") | diff --git a/docs/README.md b/docs/README.md index a4f21d8ab..24b9853d5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -50,6 +50,7 @@ covered by the section above. | Discord | _Optional_ | [Documentation](discord.md "Discord provider documentation") | | Docusign | **Required** | [Documentation](docusign.md "Docusign provider documentation") | | eBay | **Required** | [Documentation](ebay.md "eBay provider documentation") | +| Etsy | _Optional_ | [Documentation](etsy.md "Etsy provider documentation") | | EVEOnline | _Optional_ | [Documentation](eveonline.md "EVEOnline provider documentation") | | Foursquare | _Optional_ | [Documentation](foursquare.md "Foursquare provider documentation") | | GitCode | _Optional_ | [Documentation](gitcode.md "GitCode provider documentation") | diff --git a/docs/assets/Etsy-find-your-client_id.png b/docs/assets/Etsy-find-your-client_id.png new file mode 100644 index 0000000000000000000000000000000000000000..44805b48c99a1c58577676f68b2209dac04f207a GIT binary patch literal 15779 zcmeHu2Ut^Szh{I2<|1VpI)K0o4xkiKs&o|@6hsgZqy~l1i-N!eNFw71Rw6Z011h~K zB@uzdG71At3u$2qZc#H0HAZ z+0TJntdve3KkELIKW+L}<7JAU{zc)geFdRL12;dqroKMWx=*QfRQ0RJ$7)PygXiuK z{;K>%w$h=uuRs5Mp-QjSKk1{ZkKVD?v#IJiFFU2nF)n4hL-)J-Ku*I+mZT5_yP!3g zZpE99H~U1#um9`tAHf95D>&Y2 zVJ9srrqB=0p)@-lXbVaZa2AOD*1VMo#gL*y7POY3pq*}wuzK;<1H4ZEZM+ z76Tv^@Y3?bGx26>Lwj_>$5*?F{vUqcy`p^NmQ49*u6IcIbanjoZFeib&#+j%=Le{S5#MWA?@@k~p*R+En{=&{B1L&1k)M7p8dYDR-u0 z33|;QqzxtY%V^q~3lTu%9DtM>vXcWb2Lv{S+AwAzx%e4X9=4PUGGV$v*ahng~%|wKN2_34x z$fIG2xK%1Hn%(x(G}Ygf$3WqWOi3eTK`owJ8=|)HOp2jd=_@-G0Rj`;$)zdm>BI#m zkJDi5md#;j{_tWPxC9~KOy~<4`tnZ*~r%xKTsvYYQWP6QfK< zG)}WWJNx~`h2q3{C+soVal~)C;pf?H-|E(forT!;@Ub+bbZ5@}Uu zc*UPp<4J3GwgaH`OuJT?V#Qw@VykrBth*5I=a>7nWX8*55)Ozr1YiX` z9aC5&fNs3ljXRjkr-Jbd+$8JMTqty?aK?!D7KJlPRP;R5+8)pXxVU}wDb*z;eK-+4oLJgIi9qve z)XV3GO4`kRASbNsS%oByGt@@mECgp;vX=`iY73>aDc_TigsuGsJxZoReL*~Mn#v{&gS z1uURMS0BtN7}c`j^c3}$h?SeHQ5*5_M3?+(g9nl&X5IW>wX6@B(+m8aOzEcOB}g={ z4NWRO4byjk**NIgwy*H;LS6`(zY@ZpYMf`zo8qBZPyx!b@dL;b8Y~>oO^v@eF@93b&f~qbH56QUcXxm!v)z4@8Bi-!8}fq$!mKOLY~e3vMIJ zhFNqdF*$(_CivMT7Gep*rL4VbobD2+zB=9o)9=24iQ!?2@nf?M`dPG*cf6<624$5h zc6Dl^!jVqMH~W~IZyGn>titAD_!Gi{Vdu}V$I42mqAWsYf1Bpo2q_+XT&3%>Ec-C8 z(Jv3I>7zyslW;8FpR2ax&B9)yW&x@UTw1dbnyJ4LPHNpzw1!z@N7F%?ubZmP&gRxrSEuGFstRHv$v^ZA5;2v@bb#N zGKf-govnnO4H7Exrh~Gq<|g!&aSB;%I+ZL4FYxq-Qu+f4jEKefHx#9U5TD%+)^vNP z`sTv$mLt_W&f6TWfBUdnYf~BjQ8}@(kbS6R&oj^EWK-3Y56_E{-?=Y41~1FpN#GB3 z9c5Iuhrja+`MM<)#)tM9kFA&$K2X~j^HQiEs9GAcKT&hB>L{WQ@8_AH`1(~2G~|GL zewLb34RrnLT#Rr%hOD@PeST3dCig4;+OjSR{wSpg$b0jH9ZjY6${hY)&Mi}$^*|ep z+rg1IKLUbUk6kr;S#F(`MI>4RF)>Ret}TI?IHL&U`MM)LhPBJ>QCvzxGlV;~f`wDD zPc=w9m}#yrQ4KnmS-fx`jo7uXt#2gBUrnlpUc0~5e{-^tKe?L1Xp;g3H0yHJBHZ## zpP*4?KR=M|COyOHYc+kbC?)hdvBpFvN;>$>$glXAWl0<(46tak4oruexRff+T+>{j zcTn4;C2v9VR;>vciYzZjBZNt7gdwo?+IV`2@U3c~j{uUlPn0hQ#md!=dwRXay7PD6 z^_Yj<){^?rm^@9D6n1r3hYzCm8PiNcbpdD+E(2AX-i8>GzYuYe^IRT&ae)ChTW&YB zUVn_FwSk45YG!mhO5nFXqXHSEc1)V}eK&EoD8bKk`r#zIyh`VeSUZ-(rx(FIU;VUx zO>KFrwSm?CfR$vAw=St~5n9gR_2CHL#(2LOhl0mq<^K32)d+7^gj>AkiAH}UTCjwk z>5PGGR`+!IQ3wOG4Unp1dTO?A)Cdb(4dVUqaI+fUh@pGzX?>t5>zEY1q8aVfp}8V-Czu2>8Qp z&NOKBGW}MG%FxX_CTPJ&ndt@%G%3YW&$f|p#uW%@B|R4|k}i`g%@=MGdEYw2f2mYZ zvgIyOo;B&aq~(?O(CMMzMQ^O|272ShG=bXgGQ%Ckt`;dAbhq5&Ao4_rdD zh0IJ?l4KGtL$(qQyQFEe$D=qr z!-Ch+Id;QdS~MIiHH*j`pRHc_LCto^89$nd$##SdW;M!!K!t%`5)R<&dmbL?Nri%c zm07x#TZa6QQw`Kew+knBf!qj~(A2lY_B@@P>2|1E8^>~0PdztYv!BOn|MW6OrQfJN zz2jIdc*a*yL?7tL$g0>Kbmh zO31ZlP3y8E4zX$v*>lL7DW|y{Q?qeZiC{8sY!Sdj@Z?mNKkQ87_1vwi*&jJ6C|^d0nvi{E+@tpw+h;T47=P zSRJrx`q8W*pbd@FrS7%N_3-G``Hss7sPe6v%hxo!lULTPly*Q_$7Nn`!6nV%N*RGG zWA$3lv@WjTb>x(uC!)f^3g?hKv)uG%n28PbYQ8D)#shGrB*8eeUn@}8R`@l}LBgTB z`DnN`%Wt>s)!E13b6&@HeQqX(=r))LP)R}IH$ld(7nVBOpaf!~N!-X|}MAsy)$ z1|?VdF2F2)dbK6%(4$KbZTl9&> zqL=&7Cl5e{SHIa;E<84Ff`m_Z8)kDkvi0)z#*F}W?K8X?GWkICB52Em>$NA6H$QeI z;y{>u&pAErVENm|1-vKC`Uy5~26vmd zkrmG{5gY;-7so#%DY*5`K2EU;VdJX_bEg{X*zD+zkFTxJg_b~=AdW^@Ay}K3_`Rsg zIYmQH-%GFFO<1m`1>(6MY58PY!-SZip&MaMVnX*tr?J z(D4K-M3zzeUHG-*#jjjnxg_J5x|xT77&$;4NNNd9bscJ;pQ#S>SeDfUBRBVX3127MzT$s>ot44 zrIenFgP_R;QdJbX+*&(!5C!pHuFJ67c?AfCwO8&!%Huu&8fgBZoTm-KnIfS13bbV6 z9zJn~oxjB64FpzELMdjl3Sxaq4RodNj1Zb2THssAeN$Zrq*ZFTs2>8Sy1qIJ z8?#Fmn^ypJS{B|He(MY_9S&%mtlGS1hc>@jE_uIN-(`6$7S8fS#IFWmCDHNgcJR+; zd$iC~%#Sz9h7D#?n0?8siC^5>wXeJ%SB-o;=cS%6YOSxTecZ1m_>jH;H%x>XEspxc zOvgzMoR`ko{K8c&TLmM;5G+F8Dvcpv;}HwGwH*kwcgzfbHIeQ^+yNTb zWl`@!b@#b?wThLC*3ykfmWU?z*quuI@$C1>cy@9k@m;rAxpW#AmuA}Wn{0E8U_NH1 ztwVBRx9`R0{q`5R^x8!VDsi;*BZd@R$RCjB53CAD$Al|m6EgBPn-^zbo=*6ArXQK* z*UqlxpyT?@EXsQa0@lBib1gM8CUQ@l$^EMSV%76Y3OmluuYoU^KeX0lEPLXUu-B_? zsF8)*w;6lg)AzeLz%9%B%?p&CjIrVqjw|fv=PR?!^|<420b4Bh-rL=|Xm?fZrIL<| zL%rJB@R58AT5DDO1L-z%N6oR9-?zfYChTaeI?+;n+7G`QDpOwJ1`aBuhU@v%>oZrS zK+l4E8V$ej8qqZn_=WJKb0EsvE^>jHsX)qnR?#LD@Dc@X`ck#(wK?AQ5q{G_TV#!b zCq6n~+#~a>L;Zuo(_0kL`{i`=qHF541>UASwd@J^zmhsn{vdTkl+}mvK;#`k_`~j# z4@Y~vO|j1(?-p^Jy7v66awMa>=HcioZ!PS#ZGuU}DQt660UmSi6{F9w0Oiz{6m29b zX3q-R<_jW%E75Q+?r42?^@;lAR=H^9t)G-?cGMZ3nRBlkZHv-%-lnUZM!KN=R_rO* zRo~3n6ZH|T{ZTsGgWD;2KEWveq$S+*hHe8l>bd#|(y1-I=2a1!A19Q_8$X6T=zi-) zb+7kn&4{wtx-IR=Y=%WJu%IYcx63HtyF1=V z@Fcs`ZL8zUNAtZC!&NTmZ_OduPRH?rW0FavU=$e6S;=xdkY5bYOIy@c!fVA4fEgqt zJoM*zONIMY#or&j<82@AcivibGw9i8|8@93Y66D^fWs>;PM8d{KmbH2$Owp#5Hu_y zVbE?j?VEqechcVl#Pbxu&Nesqn@23H15Dq{$_=fO?m}QRNclp`k+!9Z!UX*o$_og} z53{gdIQN}|Lwl5?&=(+LL4}at#LD|0RT=7;a3XKn?aW!%Hk_HZasyYyTpyAs1E zRDmVm^9x~%kFlI};?aCI&;~uN4!dI|dQTF-*>%8$vDdQRWr=Xb*G;FcpxF=IBb%NY9{Gcb|1OcigOUC9Go z(fgAt6kvH`06ThD;sk=60JW*d^@gJ4i>=OtT+Y$8EYLM$kTnRX0&u?;s#V5G=gxGa zzCl{_*j!+tDOvaDNgBf}4EZH6&tcUE5)N|1g^#n!vn1ZQ+!#Q|528Nfucfb=HoAYS z<5IBsxih|wS&^I8|8p3MHKe3-Bi5REGqcE(m*}%z7A6kNK}dWk2B~_ts2AI=>$*%1 zWLum{H8AQdBcxU2#zGBgxUMs$bCjqOs1-2yk#D*-kYHuX@hO>xF0OENSH}`jM-5o) zl&N0`ok*(~QP#0zv^%9$khg`sl_mqr5e4442HV~H>T?zezvybu=YH;Wag7}Xc|IP+ zRS&94>E_Mjc{~+h9Ryivg$6-$LayJoN`rf)m9*>nzm`@3?hk;<`IY7fFnYJ(sFON- zxV-i9V+bL1NZ+=+%jNvro3~7gcj|Xs)RNlo;+=XZUesQ#E5~$PB6ct@VL1NrBWIIU z=>yC>@~pvSWzl?X>k9jHyUsy#DsGCtzYE&$l5s4J`)TB8f)^Ryqd>TM2Ad+kCDzHBsHM-r6{w$Tt`=`7o8*N^p+t(k5kkcSZ0cmc}?&Ref8< zU(C)#AI~UAis_4Q_e=1l((jY(Nvj>gTXl)tzzzp~=Yd-4XxWi4`VGDKNZ+n*XuDfx zH6%Ctq^hlIkX6`hy4}!`1&3n53{y4 z>SaFfJ8I9FTQ@9&ts3^kn?Vels`ETbTZS+Vo$HgZm{HoD-)`zOj9WQsDECj^r9OSy z=dMgL5zsgL=~ITqQy(!LIj6>HOdfW+q5%)o+yvn?zOA)uydrn3Cm|?2eE^lrHy>La zI}jsr%713G7OLUYK&8R8pa=JpvT+CV**0fwslh%(9=k8W%ZEr>^Ij%1F~}5Q1GI&} zjjbeWon>5kECfIE>|8$kfP}fe2T>upY1@?zwalYyFf)vKu$A$Te!>zD1ZH-)4AKi@ zyQIzRjZ@+?upF6A`UnMDN19oPwVok)XnB>k5a#Q%`AG+3`1i1?L6b>`F7wrSgHP+o zueq$6XZV{^_atairE0Iky@F_ zjuQ6s#`v)uW}h==Z(BofnjDD7GQwtVCeGjlRxQz3(pXB}tyt~1GuQ~wp*4Xe9flED z(Vu!FNG1afRQVTafU5ZZB^F^aMyrjuge^?yJet^Mc(~BN2bO|1>GTyq>5hDZY3M>RPt^yVq8sHE%i%(A>(v(d zE*lf$AAAp;-hD{1yr|aB%hX^=Z4WQ4Bs!|5cLZ` zjg$b7y81@8E=$>Cb$IeJL$>U#ye5y+DpZI2lIMR&zZz2F@&G%X&|??4Gb@ z@RJvtEI&}$wzfmPFjwn@FU?qCr0`CyVT{#f%X|`dfaj77?hfpObMv3l1}=r-#%`B) zF0@jVy)g-VG=Bn3n;?dy%@@)O^`i3AGwkQ)hLDAm5##tDZS8#9`2)D{aI3|I>Cp#e z9~~P4+oCAk98w^HS4PR95h~1MQw=c89`SeZ%DYmJ+T0>gEahlNIZfuBiUUN_+$O_G zy^)RzTa@``qQ95)agDm%p>nF-dDWf4E%LV^mxv>wwicXV`Mhi^j+5YEvHYq4G;$*C}!6tnS_S7x>|F5R8WKOfyi<6`O;63}!n zfX-fvQ;ZeP;A3QHadF-p)q6<&M&j%(!N<8|Uky~F)$7N+?w(4uy(4%Mb=bel*O|Ev z#R2o!mk!G{%~5(4z8jfI&zo2V*CbtS`fS5Zw66(dIo> zt+aC5cj=Oed~-4YV1as9=APSYC<>^ozK~;WP3I@C@QtxR%3ZCrpg+@>Uo$Yf+e5XR z8~%Z8%T1jfNSKPD(zsKJ84CNNl$bLJE~VP03w|vm)Y}Z7k^+SxQU>wqlw@B=>v>B} z;%ug`zZt7~>>;?31c{m^#QzXO2BT;1L8(U_Pq(A(0j#Q7g{B&`6gBxxxZ7*H>~e|@ zu+7K1ef?!--$|9@l+@YkewXtNTOERz|90PS`lLto!z%t6@LY?~OPS<+t}F zH7#5>w~cpf5sTE6^m@!6RCLd zIrmM}#JUWK5V$=xpVbvWDFr>{xHwww=MdpIN!2CYs`Dk6r3gPP;Vobx#?1-3w|u*aJ$Q z_?!K-barN5)!dnV>W^VpIzPEb=1BmIf{9E{%g(*leKh;p$v=lZ^Z%PcU-rB?Hdos7 z;)g$mDF)~#pS_}ox~eCB;2gM3k5uwo5aj#VfuQqQe+-j(g!-GSE;coNVHOHV`o}QI zae}`>0D%%fxUzH9Gn#-2IqcnsB4bK8_4y-uu_QjW08;=(f`Y(6yXs-)iZmk)+EzG=HKfC85 zj5~x@byB?tsYnqp(P~bo^_deyc!H5zEL^HD#~IGZe$WH-i>qx5KQcudY>b?(Idh?m z_52iTL(WeIL>u8|C}V8D0h-erl9BU(ixh4k2X$?u30-Yg;zNIRt;*cRy0{oLu z{1cLZua>``+YzHFKW^X#T%OwznCA9C@}1d16zk?!6g(w>dRWRAKpNwjuAv4QX=v7Y zN^a3`+cvu27yt$k!SF-3$o-@c2B&v!wr5UoAe%=z1%zJT8OZr8ue7N=WL&aj1#tcc z@IQH{O|9qA(+0eNJFyeI@uGN`>?M~xV%se~b4v9Qq(U^Q_-mQ2Mr*P6z1b}1&NS=( zxbS~Bd0?w?Xl18WDFcpme$NYy?AhzRfk-Qp0^p^LP)WA7`c2!=X^SZ5WrK`&KF>LYt z6m+lXo=eBK0j%gTc5S!+6Q7dby=98o^46AE-x}^|jN(?mw^02lVSOb|XxzRjOo=7O z7v>~6VZG!q8F9~mCumA-{*GwIGb2Ndg~WkVJ;%Nmi6Tm}NRjd#FikX3WV}_nK^wj= z2*{j!&H~pV-BFyw*boe9#1m|lOwj?i86FnxyG#)HqaKUgFeM+vfEkD;>iY6i{yOK^ zN^buxO*x^d``esXqS(VdhHS+idE5Z{?A~u1Q*dE3Pm*lQS)Ap`|3Yo*n63%Q6r&Jr zHrD!Qf$eW-G@p#$Si@1rG+2|-wR<8}7Ov<3F(tQQ=*Hk5&K@GnrG^DN^wUf8v#uQz z1)?b6R7KB+@=jT!^HVH=xP-(a3VVPuVO9fNC)_15E=3a~8?_y4jH+KelpPk%ISYc= zu>$*lTZ`?i@ps8PmXPQv(+{c+xf-xC7sM!jtp2#y!y`+C`%hv1_yg5r2|kp7S_VZQ z0X;~E(Ua|&CG3t6BWKHxzlN@MF#88n7H!p(`s%SW<^`fHeGj0EW$8c00b3?B=GBBB z)$Fse#FP(L)%G|E%#VG741V>lXwRN~Hj`kv@$Tw_Hh`Y^F>cH+@s1`+i z{BUS2j5r1C#}y^e9@h8SM9<)v^^Ob?|50qH+;2{;D_($~P;v9SJZNWut+iQD37oz> zt)xdP>52tazAz5Xei zm}P9`p%*{A43FUH=PkG-k4%+?%y9Y>Gv>vCEv})AE2c~lIb{?Bqx#s3oVEi2_-bDC zqU%RM&$&$l=#diWSo@4|!6YYx;w)Tqwr8#;;**Uy%+UNI@O-Ads$$#bRs!P;)Pod!amcms$sJXZuhnJ7)4SmtzdoJmKew6(WQTdOLRMR)%jL ze9{dd!(*J<5h?sAMr4tBV7cGe&GItn0xZsK-VZh8+cG>cubb`~LWydtiEixeph2gHd4M3h}Ay~ ztwvPh4pt45sZA~2Ee^SIeA@C8jd~sfScw_BDe~8p-w;*7?bHUyBj0TZoHsrC#K*5F z|0J&gycw6t@i1?B`-9L=Twvf{GaN;t`5bb=Qg{CH6nHtVqcln5QJcrZ2oq$JTeqzF zEwQ+$89szIYt0HN$;~EYA!~(+(>7|heq>>1pFS$gf4Ozq!))BOwAUF(7NSZ?3Te9d zXH61iuVd-}Sm&T zAJn&%a5z)CiTtC7+)ALI`aGH1>X~$IRHrNI(&hBY+Icyv>}y9v4*-h$r!u<)WEz`K zsSU`u9N??vR+_u@3AQ5d!9|%uYw48@4)M+^$C7&gw(K(R{)MWR&><>urV3dUl<(Se ztU|CtTnT+;=TuuPJG5FT@#{d-TokFr$L#H@MN1^?b%oVsGQ5q-$U^e^=$zhuK6hz8 zun3hDg*_{W0d_5l5;m)KU;dsg_V*o(@c z;8PU@k~#nfamJSBx}Oj$vL^=uSgYtEuKN=fjiNCA+YX9 zH3Iwo0rmv28R%{cBoeFUjj@KN^$fJ653fxI_7c)ni5<|OZ}v%lBXLtq1MoltT_gtp z&Nk~H2IoyLspNeAa$TChF^JmPdY~4M1<&p{U4s0u2Wd+?8<$?vbL=~jTjYP*LNEaH zHJ~)0KXqBZ=Y}nv>^{LCnBEL8QYHKHIP$}qqPciE@m$!MFgrVi&uO=BS$_TCfX|#v z4)87r7f3K@{9N0Z_PF`Pa#L9t)|KLi;H}|!{f;_rvEI2J6}kp0YQLQ+-RA;W0Kfx` zD6-05(xwc-7U*-LMw6$0X0_`3;l|NCJn$J1znH9ZW)#z;KtxKNh+9p_M? zW%h$rGBFw0aZ-FIDh4^iVx7R?gj^eI1SRpu@siNb;z&YD6(H8mIF7w3`gEw<^FBTMe_~dC;j*+n~ zkbb}|Oak&lOP7Iy4S*q$fkheYA9AGP6`C9Q+U1wBrYk`+Z1qbQ{~dyPomXiitxnC~ z4!_ruz8sn`AK0)D2i6uz`9=QoPpp`|h7UNR&GkwiKD*NLH{);jYIjFLBan$yEf#EOzX>&D#}00#>1B$0NqN{O!e61LP|hHB+yj-sJZhF&~*9( z?Wb9^D5MYeJem~gD$s70j`dL`mFyTl74H!)kaqTaJy_iBZ@e>Grch7*uwm`03LOzn z2KkO!unMA^_e9>2ebO}09aNT?p9Pf2HG{70)q^4+BhX%fAs$Dg34s95P`CvA-Nm%0 zs4tzqv2wN{l@xS5m$RDa-6Rw%1StNCiuf4ACrr_vRAdL1L1K`gy*r9-hNYU+4Mw~PJULIK}7@SKy1UYOr21f8# zt^p*#>1DcZ*S~tG>gIS{^KRjW;Q5m}&t>Dfu~^bSZJN|sl*oYFX!HLwya77~3>E#w zR@ClzvS?$GTe&dYF7Tf(dT-Ip0)(Wu|?zI_6C28i%f zr#SWG5#UNB#`0ZnUAEE{i?1Pk*T#It9AfUD_3J+`d*)o>mxRF+qXCKgU1rx;kd!N>TV?Tb~Ra=-8h2f7S%=bLc`<;RUcnttj zKu!bh9wK}?0pZAtuhW@lTqEsg%H>%illRs560$?vB4|7l`SUf18; z1v;ntH`Z)N0Q7Hsoa7bC-);JzeWHJw3080N+5yUYxotJa<0d+wXR!b!4A9Z1TuOoe z|Ea^cXX#Wjy;J?uC;d&ur03Snk5F68cEHd7b@(?;@Iok6J(u_c+F1?w69^FKq}}P` J4aWk0{%>A7{~-VX literal 0 HcmV?d00001 diff --git a/docs/etsy.md b/docs/etsy.md index 6c2a57d03..6394d5980 100644 --- a/docs/etsy.md +++ b/docs/etsy.md @@ -2,6 +2,27 @@ Etsy's OAuth implementation uses Authorization Code with PKCE and issues refresh tokens. This provider enables PKCE by default and validates scopes to match Etsy's requirements. +- [Integrating the Etsy Provider](#integrating-the-etsy-provider) + - [Quick Links](#quick-links) + - [Quick start](#quick-start) + - [Minimal configuration](#minimal-configuration) + - [Required Additional Settings](#required-additional-settings) + - [Optional Settings](#optional-settings) + - [Scope constants](#scope-constants) + - [Refreshing tokens](#refreshing-tokens) + - [Claims](#claims) + - [Basic User Information claims](#basic-user-information-claims) + - [Detailed User Information claims](#detailed-user-information-claims) + - [Automapped claims](#automapped-claims) + - [Manually Added Claims](#manually-added-claims) + - [Advanced Configuration](#advanced-configuration) + - [Accessing claims (Minimal API Sample)](#accessing-claims-minimal-api-sample) + - [Directly in Program.cs](#directly-in-programcs) + - [Feature-style typed Minimal API endpoints with MapGroup](#feature-style-typed-minimal-api-endpoints-with-mapgroup) + - [Extension class anywhere in your project](#extension-class-anywhere-in-your-project) + - [Sample record types](#sample-record-types) + - [Register the endpoints in Program.cs](#register-the-endpoints-in-programcs) + ## Quick Links - Register your App at [Apps You've Made](https://www.etsy.com/developers/your-apps) on Etsy. @@ -11,59 +32,103 @@ Etsy's OAuth implementation uses Authorization Code with PKCE and issues refresh ## Quick start -Add the Etsy provider in your authentication configuration and request any additional scopes you need ("shops_r" is added by default): - ```csharp -services.AddAuthentication(options => /* Auth configuration */) - .AddEtsy(options => - { - options.ClientId = builder.Configuration["Etsy:ClientId"]!; +using AspNet.Security.OAuth.Etsy; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services + .AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddEtsy(options => + { + options.ClientId = builder.Configuration["Etsy:ClientId"]!; + }); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); + +// Route to start the Etsy OAuth flow (challenge) +app.MapGet("/signin/etsy", (HttpContext ctx, string? returnUrl) => + Results.Challenge(new AuthenticationProperties + { + RedirectUri = returnUrl ?? "/" + }, new[] { EtsyAuthenticationDefaults.AuthenticationScheme })); + +// NOTE: The callback path '/signin-etsy' is handled automatically by the middleware. +// Do NOT map a route for it unless you change CallbackPath in options. + +app.Run(); +``` + +### Minimal configuration + +**In your appsettings.json or appsettings.Development.json file:** + +```json +{ + "Etsy": { + "ClientId": "your-etsy-api-key" + } +} +``` - // Optional: request additional scopes - options.Scope.Add(AspNet.Security.OAuth.Etsy.EtsyAuthenticationConstants.Scopes.ListingsRead); +**In your `Program.cs` or `Startup.cs` file:** - // Optional: fetch extended profile (requires email_r) - // options.IncludeDetailedUserInfo = true; - // options.Scope.Add(AspNet.Security.OAuth.Etsy.EtsyAuthenticationConstants.Scopes.EmailRead); - }); +```csharp +builder.Services.Configure( + builder.Configuration.GetSection("Etsy")); ``` ## Required Additional Settings -_None._ +- `ClientId` is required. + + You can obtain it by registering your application on [Etsy's developer portal](https://www.etsy.com/developers/your-apps). + + It will be stated as `keystring` in your app settings: + + ![Etsy-find-your-client_id](./assets/Etsy-find-your-client_id.png) > [!NOTE] > -> - ClientSecret is optional for apps registered with Personal Access (public client); Etsy's flow uses Authorization Code with PKCE. -> - PKCE is required and is enabled by default. -> - The default callback path is `/signin-etsy`. -> - Etsy requires at least one scope; `shops_r` must always be included and is added by default. -> - To call the [`getUser` endpoint](https://developers.etsy.com/documentation/reference/#operation/getUser) or when `IncludeDetailedUserInfo` is enabled, add `email_r`. +> - ClientSecret is optional for public clients using PKCE. +> - When `IncludeDetailedUserInfo` is enabled, `email_r` scope and standard claims are auto-mapped. +> - The `EtsyAuthenticationConstants.Claims.ImageUrl` claim must be [added if needed](#manually-added-claims). ## Optional Settings | Property Name | Property Type | Description | Default Value | -|:--|:--|:--|:--| -| `Scope` | `ICollection` | Scopes to request. At least one scope is required and `shops_r` must be included (it is added by default). Add `email_r` if you enable `IncludeDetailedUserInfo`. | `["shops_r"]` | -| `IncludeDetailedUserInfo` | `bool` | Makes a second API call to fetch extended profile data (requires `email_r`). | `false` | -| `AccessType` | `EtsyAuthenticationAccessType` | Apps registered as `Personal Access` don't require the client secret in [Authorization Code Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1). | `Personal` | -| `SaveTokens` | `bool` | Persists access/refresh tokens (required by Etsy and validated). | `true` | +|:--:|:--:|:--:|:--:| +| `Scope` | `ICollection` | Scopes to request. Use `EtsyAuthenticationConstants.Scopes.*` constants. | `["shops_r"]` | +| `IncludeDetailedUserInfo` | `bool` | Fetch extended profile data with auto-mapped claims (Email, GivenName, Surname). | `false` | +| `UsePkce` | `bool` | Enable PKCE (required by Etsy). | `true` | +| `SaveTokens` | `bool` | Persist access and refresh tokens. | `true` | +| `CallbackPath` | `PathString` | The request path within your application where the user-agent will be returned after Etsy has authenticated the user. | `/signin-etsy` | +| `DetailedUserInfoEndpoint` | `string` | The endpoint to retrieve detailed user information. | `https://openapi.etsy.com/v3/application/users/{0}` | + +> [!NOTE] +> The `DetailedUserInfoEndpoint` uses `{0}` as a placeholder for the `user_id`. It's replaced automatically when fetching detailed user info. ### Scope constants Use `EtsyAuthenticationConstants.Scopes.*` instead of string literals. Common values: -- `EmailRead` → `email_r` -- `ListingsRead` → `listings_r` -- `ListingsWrite` → `listings_w` -- `ShopsRead` → `shops_r` -- `TransactionsRead` → `transactions_r` - -## Validation behavior - -- PKCE and token saving are required and enforced by the options validator. -- Validation fails if no scopes are requested or if `shops_r` is missing. -- If `IncludeDetailedUserInfo` is true, `email_r` must be present. +| Constant | Scope Value | +|:--|:--| +| `EmailRead` | `email_r` | +| `ListingsRead` | `listings_r` | +| `ListingsWrite` | `listings_w` | +| `ShopsRead` | `shops_r` | +| `TransactionsRead` | `transactions_r` | ## Refreshing tokens @@ -77,118 +142,112 @@ See [Requesting a Refresh OAuth Token](#quick-links) in the Quick Links above fo ## Claims -Basic claims are populated from `/v3/application/users/me`. When `IncludeDetailedUserInfo` is enabled and `email_r` is granted, additional claims are populated from `/v3/application/users/{user_id}`. +### Basic User Information claims + +**Endpoint:** [`/v3/application/users/me` `getMe`](https://developers.etsy.com/documentation/reference#operation/getMe) | Claim Type | Value Source | Description | -|:--|:--|:--| +|:--|:--:|:--:| | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | `user_id` | Primary user identifier | -| `urn:etsy:user_id` | `user_id` | Etsy-specific user ID claim (in addition to NameIdentifier) | | `urn:etsy:shop_id` | `shop_id` | User's shop ID | -| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | `primary_email` | Primary email address | -| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | `first_name` | First name | -| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | `last_name` | Last name | -| `urn:etsy:primary_email` | `primary_email` | Etsy-specific email claim | -| `urn:etsy:first_name` | `first_name` | Etsy-specific first name claim | -| `urn:etsy:last_name` | `last_name` | Etsy-specific last name claim | -| `urn:etsy:image_url` | `image_url_75x75` | 75x75 profile image URL | -## Configuration +### Detailed User Information claims -### Minimal configuration - -#### [Program.cs](#tab/minimal-configuration-program) +Endpoint: [`/v3/application/users/{user_id}` `getUser`](https://developers.etsy.com/documentation/reference#operation/getUser) -```csharp -using AspNet.Security.OAuth.Etsy; -using Microsoft.AspNetCore.Authentication.Cookies; +#### Automapped claims -var builder = WebApplication.CreateBuilder(args); +_Requires `EtsyAuthenticationOptions.IncludeDetailedUserInfo = true`_ -builder.Services.AddAuthentication(options => -{ - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme; -}) -.AddCookie() -.AddEtsy(options => -{ - options.ClientId = builder.Configuration["Etsy:ClientId"]!; +| Claim Type | JSON Key | Auto-mapped | +|:--|:--:|:--:| +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | `primary_email` | ✓ | +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | `first_name` | ✓ | +| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | `last_name` | ✓ | +| `urn:etsy:image_url` | `image_url_75x75` | [Manual](#manually-added-claims) | - // Enable extended profile (requires email_r) - // options.IncludeDetailedUserInfo = true; - // options.Scope.Add(EtsyAuthenticationConstants.Scopes.EmailRead); +#### Manually Added Claims - // Add other optional scopes (shops_r is added by default) -}); +The `image_url_75x75` claim is not auto-mapped to reduce data bloat. You can add it manually via either: -var app = builder.Build(); +**Direct JSON key mapping:** -app.UseAuthentication(); -app.UseAuthorization(); +This sample does also work for regular JSON key mapping: -app.Run(); +```csharp +options.ClaimActions.MapJsonKey(EtsyAuthenticationConstants.Claims.ImageUrl, "image_url_75x75"); ``` -#### [appsettings.json or appsettings.Development.json](#tab/minimal-configuration-appsettings) +**Claim Image using predefined extension method:** -```json -{ - "Etsy": { - "ClientId": "your-etsy-api-key" - } -} +```csharp +options.ClaimActions.MapImageClaim(); ``` -*** +## Advanced Configuration -### Advanced using App Settings +The Etsy authentication handler can be configured in code or via configuration files. -You can keep using code-based configuration, or bind from configuration values. Here is a comprehensive `appsettings.json` example covering supported options and common scopes: +> [!NOTE] +> Always make sure to use proper [Secret Management for production applications](https://learn.microsoft.com/aspnet/core/security/app-secrets). + +You can keep using code-based configuration, or bind from configuration values. + +> [!WARNING] +> Avoid setting `UsePkce` from configuration, as Etsy requires PKCE for all OAuth flows. + +Here is a comprehensive `appsettings.json` example covering supported options and common scopes: ```json { "Etsy": { "ClientId": "your-etsy-api-key", - "AccessType": "Personal", "IncludeDetailedUserInfo": true, "SaveTokens": true, "Scopes": [ "shops_r", "email_r" ] - }, - "Logging": { - "LogLevel": { "Default": "Information" } } } ``` -If you bind from configuration, set the options in code, for example: +> [!NOTE] +> We recommend saving tokens (`SaveTokens = true`) to facilitate token refresh, so the user does not need to re-authenticate frequently. +> [!NOTE] +> If you bind from appsettings, make sure to either set no scopes, if the basic `shops_r` scope is sufficient (`IncludeDetailedUserInfo` set to `true` would also add `email_r`), or always include them into the configuration. The handler does not automatically add it when binding from configuration. + +If you bind then from configuration, set the options in code, for example: ```csharp +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme; +}) +.AddCookie() .AddEtsy(options => { - var section = builder.Configuration.GetSection("Etsy"); - options.ClientId = section["ClientId"]!; - options.AccessType = Enum.Parse(section["AccessType"] ?? "Personal", true); - options.IncludeDetailedUserInfo = bool.TryParse(section["IncludeDetailedUserInfo"], out var detailed) && detailed; - options.SaveTokens = !bool.TryParse(section["SaveTokens"], out var save) || save; // defaults to true + var section = builder.Configuration.GetSection("Etsy").Get(); + options.ClientId = section.ClientId!; + options.IncludeDetailedUserInfo = section.IncludeDetailedUserInfo; + options.SaveTokens = section.SaveTokens; // Apply scopes from config if present var scopes = section.GetSection("Scopes").Get(); - if (scopes is { Length: > 0 }) + + foreach (var scope in scopes) { - foreach (var scope in scopes) - { - options.Scope.Add(scope); - } + options.Scope.Add(scope); } + + // Optionally add image claim + options.AddImageClaim(); }) ``` -> [!NOTE] -> Make sure to use proper [Secret Management for production applications](https://learn.microsoft.com/aspnet/core/security/app-secrets). +## Accessing claims (Minimal API Sample) -## Accessing claims +If you want to access the claims provided by the Etsy provider, you can set up some Minimal API endpoints like this: -**Using Minimal API:** +### Directly in Program.cs ```csharp using AspNet.Security.OAuth.Etsy; @@ -207,7 +266,9 @@ app.MapGet("/profile", (ClaimsPrincipal user) => }).RequireAuthorization(); ``` -## Feature-style typed Minimal API endpoints with MapGroup +### Feature-style typed Minimal API endpoints with MapGroup + +#### Extension class anywhere in your project ```csharp using AspNet.Security.OAuth.Etsy; @@ -287,7 +348,11 @@ public static class EtsyAuthEndpoints return TypedResults.Ok(tokenInfo); } +``` +#### Sample record types + +```csharp public sealed record UserInfo { public required string UserId { get; init; } @@ -306,3 +371,10 @@ public static class EtsyAuthEndpoints } } ``` + +#### Register the endpoints in Program.cs + +```csharp +using MyApi.Features.Authorization; +app.MapEtsyAuth(); +``` diff --git a/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj b/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj index c6b7b37ad..c779f577f 100644 --- a/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj +++ b/src/AspNet.Security.OAuth.Etsy/AspNet.Security.OAuth.Etsy.csproj @@ -9,7 +9,7 @@ ASP.NET Core security middleware enabling Etsy authentication. - Sonja + Sonja Schweitzer aspnetcore;authentication;etsy;oauth;security From 2bd6bef132f13516071597b5ccf3d4e6e41bacb3 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 23:40:26 +0100 Subject: [PATCH 23/32] chore: xml docs updates and update bundle.json with the placeholder value --- .../EtsyAuthenticationDefaults.cs | 2 +- .../EtsyAuthenticationOptions.cs | 8 ++++++++ .../Etsy/bundle.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs index 9882b34bd..f33ef54dc 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationDefaults.cs @@ -49,7 +49,7 @@ public static class EtsyAuthenticationDefaults public static readonly string UserInformationEndpoint = "https://openapi.etsy.com/v3/application/users/me"; /// - /// Default value for receiving the user profile based upon a unique user IDgetUser. + /// Default value for receiving the user profile based upon a unique user ID getUser. /// public static readonly CompositeFormat DetailedUserInfoEndpoint = CompositeFormat.Parse("https://openapi.etsy.com/v3/application/users/{0}"); } diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index 87210929a..ee43b2cae 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -40,6 +40,14 @@ public EtsyAuthenticationOptions() /// public bool IncludeDetailedUserInfo { get; set; } + /// + /// Gets or sets the endpoint used to retrieve detailed user information. + /// + /// + /// The placeholder for client_id needs to be "{0}" and will be replaced with the authenticated user's ID. + /// + public string? DetailedUserInfoEndpoint { get; set; } + /// public override void Validate() { diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json index 44544e3a6..bcbf4ab8c 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json @@ -24,7 +24,7 @@ }, { "comment": "Etsy /v3/application/users/{user_id} endpoint - returns detailed user information", - "uri": "https://openapi.etsy.com/v3/application/users/123456", + "uri": "https://openapi.etsy.com/v3/application/users/{0}", "contentFormat": "json", "contentJson": { "user_id": 123456, From 1dba2faf2ec110ed7deb24a0d80a0676db2af28a Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Fri, 7 Nov 2025 23:43:51 +0100 Subject: [PATCH 24/32] chore: implement Options fed DetailedUserInfoEndpoint and set fallback to defaults in handler --- .../EtsyAuthenticationHandler.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs index 984321572..4a4fd51ba 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs @@ -99,20 +99,29 @@ protected override async Task CreateTicketAsync( /// A containing the detailed user information. protected virtual async Task GetDetailedUserInfoAsync([NotNull] OAuthTokenResponse tokens, long userId) { - var userDetailsUrl = string.Format(null, EtsyAuthenticationDefaults.DetailedUserInfoEndpoint, userId); + string userDetailsUrl; + if (!string.IsNullOrWhiteSpace(Options.DetailedUserInfoEndpoint)) + { + userDetailsUrl = string.Format(CultureInfo.InvariantCulture, Options.DetailedUserInfoEndpoint, userId); + } + else + { + userDetailsUrl = string.Format(CultureInfo.InvariantCulture, EtsyAuthenticationDefaults.DetailedUserInfoEndpoint, userId); + } + using var request = new HttpRequestMessage(HttpMethod.Get, userDetailsUrl); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); request.Headers.Add("x-api-key", Options.ClientId); - using var userResponse = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); - if (!userResponse.IsSuccessStatusCode) + using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); + if (!response.IsSuccessStatusCode) { - await Log.UserProfileErrorAsync(Logger, userResponse, Context.RequestAborted); + await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted); throw new HttpRequestException("An error occurred while retrieving detailed user info from Etsy."); } - return JsonDocument.Parse(await userResponse.Content.ReadAsStringAsync(Context.RequestAborted)); + return JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted)); } private static partial class Log From 1502976d56cb318ebbb0f32789b2f8071a265192 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Sat, 8 Nov 2025 00:46:15 +0100 Subject: [PATCH 25/32] revert: removed unpurposely added arch specific builds from sln file --- AspNet.Security.OAuth.Providers.sln | 886 +--------------------------- 1 file changed, 1 insertion(+), 885 deletions(-) diff --git a/AspNet.Security.OAuth.Providers.sln b/AspNet.Security.OAuth.Providers.sln index 1d68261a7..aed80739d 100644 --- a/AspNet.Security.OAuth.Providers.sln +++ b/AspNet.Security.OAuth.Providers.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31825.309 @@ -337,1333 +337,449 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|x64.ActiveCfg = Debug|Any CPU - {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|x64.Build.0 = Debug|Any CPU - {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|x86.ActiveCfg = Debug|Any CPU - {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Debug|x86.Build.0 = Debug|Any CPU {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|Any CPU.Build.0 = Release|Any CPU - {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|x64.ActiveCfg = Release|Any CPU - {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|x64.Build.0 = Release|Any CPU - {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|x86.ActiveCfg = Release|Any CPU - {9DFEE19E-C170-4F20-93F3-EC952B1532F2}.Release|x86.Build.0 = Release|Any CPU {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|x64.ActiveCfg = Debug|Any CPU - {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|x64.Build.0 = Debug|Any CPU - {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|x86.ActiveCfg = Debug|Any CPU - {0351689C-EAB7-42D0-966F-4021B89EF553}.Debug|x86.Build.0 = Debug|Any CPU {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|Any CPU.ActiveCfg = Release|Any CPU {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|Any CPU.Build.0 = Release|Any CPU - {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|x64.ActiveCfg = Release|Any CPU - {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|x64.Build.0 = Release|Any CPU - {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|x86.ActiveCfg = Release|Any CPU - {0351689C-EAB7-42D0-966F-4021B89EF553}.Release|x86.Build.0 = Release|Any CPU {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|x64.ActiveCfg = Debug|Any CPU - {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|x64.Build.0 = Debug|Any CPU - {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|x86.ActiveCfg = Debug|Any CPU - {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Debug|x86.Build.0 = Debug|Any CPU {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|Any CPU.ActiveCfg = Release|Any CPU {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|Any CPU.Build.0 = Release|Any CPU - {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|x64.ActiveCfg = Release|Any CPU - {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|x64.Build.0 = Release|Any CPU - {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|x86.ActiveCfg = Release|Any CPU - {38E5DB77-7F35-4E3C-9719-8A25CE4130CA}.Release|x86.Build.0 = Release|Any CPU {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|x64.ActiveCfg = Debug|Any CPU - {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|x64.Build.0 = Debug|Any CPU - {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|x86.ActiveCfg = Debug|Any CPU - {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Debug|x86.Build.0 = Debug|Any CPU {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|Any CPU.Build.0 = Release|Any CPU - {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|x64.ActiveCfg = Release|Any CPU - {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|x64.Build.0 = Release|Any CPU - {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|x86.ActiveCfg = Release|Any CPU - {F79E8D15-F296-424C-A50E-AE39EB2C609F}.Release|x86.Build.0 = Release|Any CPU {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|x64.ActiveCfg = Debug|Any CPU - {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|x64.Build.0 = Debug|Any CPU - {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|x86.ActiveCfg = Debug|Any CPU - {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Debug|x86.Build.0 = Debug|Any CPU {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|Any CPU.Build.0 = Release|Any CPU - {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|x64.ActiveCfg = Release|Any CPU - {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|x64.Build.0 = Release|Any CPU - {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|x86.ActiveCfg = Release|Any CPU - {BE72BFAD-2B7A-43D6-9B75-21456852C63A}.Release|x86.Build.0 = Release|Any CPU {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|x64.ActiveCfg = Debug|Any CPU - {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|x64.Build.0 = Debug|Any CPU - {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|x86.ActiveCfg = Debug|Any CPU - {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Debug|x86.Build.0 = Debug|Any CPU {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|Any CPU.Build.0 = Release|Any CPU - {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|x64.ActiveCfg = Release|Any CPU - {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|x64.Build.0 = Release|Any CPU - {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|x86.ActiveCfg = Release|Any CPU - {9E09DCBC-20DB-4CD8-8A6D-5FA1958EEF14}.Release|x86.Build.0 = Release|Any CPU {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|x64.ActiveCfg = Debug|Any CPU - {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|x64.Build.0 = Debug|Any CPU - {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|x86.ActiveCfg = Debug|Any CPU - {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Debug|x86.Build.0 = Debug|Any CPU {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|Any CPU.ActiveCfg = Release|Any CPU {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|Any CPU.Build.0 = Release|Any CPU - {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|x64.ActiveCfg = Release|Any CPU - {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|x64.Build.0 = Release|Any CPU - {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|x86.ActiveCfg = Release|Any CPU - {01CFAA4D-C8E9-45A3-B8A6-3AF0BEC9048C}.Release|x86.Build.0 = Release|Any CPU {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|x64.ActiveCfg = Debug|Any CPU - {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|x64.Build.0 = Debug|Any CPU - {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|x86.ActiveCfg = Debug|Any CPU - {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Debug|x86.Build.0 = Debug|Any CPU {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|Any CPU.Build.0 = Release|Any CPU - {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|x64.ActiveCfg = Release|Any CPU - {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|x64.Build.0 = Release|Any CPU - {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|x86.ActiveCfg = Release|Any CPU - {43FDCE3F-5554-48EF-BB3D-1E8E7022E0C6}.Release|x86.Build.0 = Release|Any CPU {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|x64.ActiveCfg = Debug|Any CPU - {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|x64.Build.0 = Debug|Any CPU - {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|x86.ActiveCfg = Debug|Any CPU - {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Debug|x86.Build.0 = Debug|Any CPU {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|Any CPU.ActiveCfg = Release|Any CPU {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|Any CPU.Build.0 = Release|Any CPU - {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|x64.ActiveCfg = Release|Any CPU - {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|x64.Build.0 = Release|Any CPU - {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|x86.ActiveCfg = Release|Any CPU - {80F57DC9-D79D-4112-AD68-5037EA0F08FB}.Release|x86.Build.0 = Release|Any CPU {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|x64.ActiveCfg = Debug|Any CPU - {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|x64.Build.0 = Debug|Any CPU - {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|x86.ActiveCfg = Debug|Any CPU - {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Debug|x86.Build.0 = Debug|Any CPU {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|Any CPU.Build.0 = Release|Any CPU - {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|x64.ActiveCfg = Release|Any CPU - {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|x64.Build.0 = Release|Any CPU - {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|x86.ActiveCfg = Release|Any CPU - {535CD0A4-2117-4576-8552-4188D7E8E3D4}.Release|x86.Build.0 = Release|Any CPU {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|x64.ActiveCfg = Debug|Any CPU - {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|x64.Build.0 = Debug|Any CPU - {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|x86.ActiveCfg = Debug|Any CPU - {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Debug|x86.Build.0 = Debug|Any CPU {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|Any CPU.ActiveCfg = Release|Any CPU {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|Any CPU.Build.0 = Release|Any CPU - {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|x64.ActiveCfg = Release|Any CPU - {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|x64.Build.0 = Release|Any CPU - {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|x86.ActiveCfg = Release|Any CPU - {95266F79-613A-42C4-96E2-E1435AD9ED6E}.Release|x86.Build.0 = Release|Any CPU {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|x64.ActiveCfg = Debug|Any CPU - {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|x64.Build.0 = Debug|Any CPU - {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|x86.ActiveCfg = Debug|Any CPU - {E323D375-7972-4892-AD38-CEB8F512F3D2}.Debug|x86.Build.0 = Debug|Any CPU {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|Any CPU.Build.0 = Release|Any CPU - {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|x64.ActiveCfg = Release|Any CPU - {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|x64.Build.0 = Release|Any CPU - {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|x86.ActiveCfg = Release|Any CPU - {E323D375-7972-4892-AD38-CEB8F512F3D2}.Release|x86.Build.0 = Release|Any CPU {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|x64.ActiveCfg = Debug|Any CPU - {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|x64.Build.0 = Debug|Any CPU - {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|x86.ActiveCfg = Debug|Any CPU - {4D3B96B2-89A2-4069-A758-E3547B76666A}.Debug|x86.Build.0 = Debug|Any CPU {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|Any CPU.Build.0 = Release|Any CPU - {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|x64.ActiveCfg = Release|Any CPU - {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|x64.Build.0 = Release|Any CPU - {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|x86.ActiveCfg = Release|Any CPU - {4D3B96B2-89A2-4069-A758-E3547B76666A}.Release|x86.Build.0 = Release|Any CPU {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|x64.ActiveCfg = Debug|Any CPU - {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|x64.Build.0 = Debug|Any CPU - {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|x86.ActiveCfg = Debug|Any CPU - {9538F27E-1E40-41DB-B478-39E0458B933F}.Debug|x86.Build.0 = Debug|Any CPU {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|Any CPU.Build.0 = Release|Any CPU - {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|x64.ActiveCfg = Release|Any CPU - {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|x64.Build.0 = Release|Any CPU - {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|x86.ActiveCfg = Release|Any CPU - {9538F27E-1E40-41DB-B478-39E0458B933F}.Release|x86.Build.0 = Release|Any CPU {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|x64.ActiveCfg = Debug|Any CPU - {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|x64.Build.0 = Debug|Any CPU - {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|x86.ActiveCfg = Debug|Any CPU - {D506276B-5719-474A-A868-15ED65B4D3B6}.Debug|x86.Build.0 = Debug|Any CPU {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|Any CPU.Build.0 = Release|Any CPU - {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|x64.ActiveCfg = Release|Any CPU - {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|x64.Build.0 = Release|Any CPU - {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|x86.ActiveCfg = Release|Any CPU - {D506276B-5719-474A-A868-15ED65B4D3B6}.Release|x86.Build.0 = Release|Any CPU {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|x64.ActiveCfg = Debug|Any CPU - {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|x64.Build.0 = Debug|Any CPU - {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|x86.ActiveCfg = Debug|Any CPU - {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Debug|x86.Build.0 = Debug|Any CPU {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|Any CPU.ActiveCfg = Release|Any CPU {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|Any CPU.Build.0 = Release|Any CPU - {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|x64.ActiveCfg = Release|Any CPU - {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|x64.Build.0 = Release|Any CPU - {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|x86.ActiveCfg = Release|Any CPU - {A0F9AC43-6E42-4E92-92A3-85C2E5CC0986}.Release|x86.Build.0 = Release|Any CPU {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|x64.ActiveCfg = Debug|Any CPU - {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|x64.Build.0 = Debug|Any CPU - {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|x86.ActiveCfg = Debug|Any CPU - {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Debug|x86.Build.0 = Debug|Any CPU {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|Any CPU.Build.0 = Release|Any CPU - {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|x64.ActiveCfg = Release|Any CPU - {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|x64.Build.0 = Release|Any CPU - {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|x86.ActiveCfg = Release|Any CPU - {DC58F830-EEE1-45BE-B5F4-AB2222E744A5}.Release|x86.Build.0 = Release|Any CPU {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|x64.ActiveCfg = Debug|Any CPU - {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|x64.Build.0 = Debug|Any CPU - {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|x86.ActiveCfg = Debug|Any CPU - {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Debug|x86.Build.0 = Debug|Any CPU {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|Any CPU.ActiveCfg = Release|Any CPU {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|Any CPU.Build.0 = Release|Any CPU - {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|x64.ActiveCfg = Release|Any CPU - {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|x64.Build.0 = Release|Any CPU - {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|x86.ActiveCfg = Release|Any CPU - {6299EB3E-5E8C-4D54-AAEB-7B825C96411E}.Release|x86.Build.0 = Release|Any CPU {C20BE880-52CB-491C-977C-F08702376766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C20BE880-52CB-491C-977C-F08702376766}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C20BE880-52CB-491C-977C-F08702376766}.Debug|x64.ActiveCfg = Debug|Any CPU - {C20BE880-52CB-491C-977C-F08702376766}.Debug|x64.Build.0 = Debug|Any CPU - {C20BE880-52CB-491C-977C-F08702376766}.Debug|x86.ActiveCfg = Debug|Any CPU - {C20BE880-52CB-491C-977C-F08702376766}.Debug|x86.Build.0 = Debug|Any CPU {C20BE880-52CB-491C-977C-F08702376766}.Release|Any CPU.ActiveCfg = Release|Any CPU {C20BE880-52CB-491C-977C-F08702376766}.Release|Any CPU.Build.0 = Release|Any CPU - {C20BE880-52CB-491C-977C-F08702376766}.Release|x64.ActiveCfg = Release|Any CPU - {C20BE880-52CB-491C-977C-F08702376766}.Release|x64.Build.0 = Release|Any CPU - {C20BE880-52CB-491C-977C-F08702376766}.Release|x86.ActiveCfg = Release|Any CPU - {C20BE880-52CB-491C-977C-F08702376766}.Release|x86.Build.0 = Release|Any CPU {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|x64.ActiveCfg = Debug|Any CPU - {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|x64.Build.0 = Debug|Any CPU - {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|x86.ActiveCfg = Debug|Any CPU - {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Debug|x86.Build.0 = Debug|Any CPU {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|Any CPU.Build.0 = Release|Any CPU - {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|x64.ActiveCfg = Release|Any CPU - {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|x64.Build.0 = Release|Any CPU - {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|x86.ActiveCfg = Release|Any CPU - {6BC54AC2-5273-417E-B7E5-EF990F7A4A37}.Release|x86.Build.0 = Release|Any CPU {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|x64.ActiveCfg = Debug|Any CPU - {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|x64.Build.0 = Debug|Any CPU - {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|x86.ActiveCfg = Debug|Any CPU - {244F6383-BD8E-4B32-8741-F2F114499B1A}.Debug|x86.Build.0 = Debug|Any CPU {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|Any CPU.Build.0 = Release|Any CPU - {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|x64.ActiveCfg = Release|Any CPU - {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|x64.Build.0 = Release|Any CPU - {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|x86.ActiveCfg = Release|Any CPU - {244F6383-BD8E-4B32-8741-F2F114499B1A}.Release|x86.Build.0 = Release|Any CPU {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|x64.ActiveCfg = Debug|Any CPU - {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|x64.Build.0 = Debug|Any CPU - {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|x86.ActiveCfg = Debug|Any CPU - {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Debug|x86.Build.0 = Debug|Any CPU {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|Any CPU.Build.0 = Release|Any CPU - {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|x64.ActiveCfg = Release|Any CPU - {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|x64.Build.0 = Release|Any CPU - {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|x86.ActiveCfg = Release|Any CPU - {9AA5F2CD-3AC4-4177-A8FE-82D67A0F36AC}.Release|x86.Build.0 = Release|Any CPU {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|x64.ActiveCfg = Debug|Any CPU - {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|x64.Build.0 = Debug|Any CPU - {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|x86.ActiveCfg = Debug|Any CPU - {AC651C2B-C879-41E2-96E0-78D3F0888246}.Debug|x86.Build.0 = Debug|Any CPU {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|Any CPU.Build.0 = Release|Any CPU - {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|x64.ActiveCfg = Release|Any CPU - {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|x64.Build.0 = Release|Any CPU - {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|x86.ActiveCfg = Release|Any CPU - {AC651C2B-C879-41E2-96E0-78D3F0888246}.Release|x86.Build.0 = Release|Any CPU {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|x64.ActiveCfg = Debug|Any CPU - {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|x64.Build.0 = Debug|Any CPU - {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|x86.ActiveCfg = Debug|Any CPU - {1302E67D-5448-407B-8B8D-709A0BA458E8}.Debug|x86.Build.0 = Debug|Any CPU {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|Any CPU.Build.0 = Release|Any CPU - {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|x64.ActiveCfg = Release|Any CPU - {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|x64.Build.0 = Release|Any CPU - {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|x86.ActiveCfg = Release|Any CPU - {1302E67D-5448-407B-8B8D-709A0BA458E8}.Release|x86.Build.0 = Release|Any CPU {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|x64.ActiveCfg = Debug|Any CPU - {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|x64.Build.0 = Debug|Any CPU - {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|x86.ActiveCfg = Debug|Any CPU - {5881BB63-89BD-4397-A61C-69B1844F7615}.Debug|x86.Build.0 = Debug|Any CPU {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|Any CPU.ActiveCfg = Release|Any CPU {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|Any CPU.Build.0 = Release|Any CPU - {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|x64.ActiveCfg = Release|Any CPU - {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|x64.Build.0 = Release|Any CPU - {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|x86.ActiveCfg = Release|Any CPU - {5881BB63-89BD-4397-A61C-69B1844F7615}.Release|x86.Build.0 = Release|Any CPU {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|x64.ActiveCfg = Debug|Any CPU - {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|x64.Build.0 = Debug|Any CPU - {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|x86.ActiveCfg = Debug|Any CPU - {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Debug|x86.Build.0 = Debug|Any CPU {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|Any CPU.ActiveCfg = Release|Any CPU {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|Any CPU.Build.0 = Release|Any CPU - {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|x64.ActiveCfg = Release|Any CPU - {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|x64.Build.0 = Release|Any CPU - {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|x86.ActiveCfg = Release|Any CPU - {B332222F-43B4-4B3E-8EB3-9BCE29901043}.Release|x86.Build.0 = Release|Any CPU {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|x64.ActiveCfg = Debug|Any CPU - {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|x64.Build.0 = Debug|Any CPU - {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|x86.ActiveCfg = Debug|Any CPU - {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Debug|x86.Build.0 = Debug|Any CPU {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|Any CPU.Build.0 = Release|Any CPU - {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|x64.ActiveCfg = Release|Any CPU - {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|x64.Build.0 = Release|Any CPU - {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|x86.ActiveCfg = Release|Any CPU - {B953D0C1-4C31-489D-A6C6-56A7C60AA3C6}.Release|x86.Build.0 = Release|Any CPU {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|Any CPU.Build.0 = Debug|Any CPU - {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|x64.ActiveCfg = Debug|Any CPU - {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|x64.Build.0 = Debug|Any CPU - {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|x86.ActiveCfg = Debug|Any CPU - {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Debug|x86.Build.0 = Debug|Any CPU {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|Any CPU.ActiveCfg = Release|Any CPU {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|Any CPU.Build.0 = Release|Any CPU - {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|x64.ActiveCfg = Release|Any CPU - {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|x64.Build.0 = Release|Any CPU - {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|x86.ActiveCfg = Release|Any CPU - {571B6C99-2EE7-4300-9E0D-20A3A9C36647}.Release|x86.Build.0 = Release|Any CPU {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|x64.ActiveCfg = Debug|Any CPU - {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|x64.Build.0 = Debug|Any CPU - {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|x86.ActiveCfg = Debug|Any CPU - {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Debug|x86.Build.0 = Debug|Any CPU {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|Any CPU.Build.0 = Release|Any CPU - {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|x64.ActiveCfg = Release|Any CPU - {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|x64.Build.0 = Release|Any CPU - {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|x86.ActiveCfg = Release|Any CPU - {6CD2697C-CF93-4A00-8ABF-D3EFF0B9DFF9}.Release|x86.Build.0 = Release|Any CPU {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|x64.ActiveCfg = Debug|Any CPU - {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|x64.Build.0 = Debug|Any CPU - {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|x86.ActiveCfg = Debug|Any CPU - {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Debug|x86.Build.0 = Debug|Any CPU {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|Any CPU.Build.0 = Release|Any CPU - {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|x64.ActiveCfg = Release|Any CPU - {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|x64.Build.0 = Release|Any CPU - {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|x86.ActiveCfg = Release|Any CPU - {6FE602FD-DD5F-4BE3-807C-9B13D3B718CE}.Release|x86.Build.0 = Release|Any CPU {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|x64.ActiveCfg = Debug|Any CPU - {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|x64.Build.0 = Debug|Any CPU - {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|x86.ActiveCfg = Debug|Any CPU - {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Debug|x86.Build.0 = Debug|Any CPU {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|Any CPU.Build.0 = Release|Any CPU - {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|x64.ActiveCfg = Release|Any CPU - {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|x64.Build.0 = Release|Any CPU - {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|x86.ActiveCfg = Release|Any CPU - {AA05963A-91BA-4B9D-A532-B280E5A9AB88}.Release|x86.Build.0 = Release|Any CPU {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|x64.ActiveCfg = Debug|Any CPU - {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|x64.Build.0 = Debug|Any CPU - {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|x86.ActiveCfg = Debug|Any CPU - {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Debug|x86.Build.0 = Debug|Any CPU {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|Any CPU.Build.0 = Release|Any CPU - {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|x64.ActiveCfg = Release|Any CPU - {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|x64.Build.0 = Release|Any CPU - {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|x86.ActiveCfg = Release|Any CPU - {D0713EFF-8FBA-4145-8E57-5DF1CFBBC0EF}.Release|x86.Build.0 = Release|Any CPU {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|x64.ActiveCfg = Debug|Any CPU - {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|x64.Build.0 = Debug|Any CPU - {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|x86.ActiveCfg = Debug|Any CPU - {F276EB53-7758-4CEE-8013-61BA537B94C6}.Debug|x86.Build.0 = Debug|Any CPU {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|Any CPU.Build.0 = Release|Any CPU - {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|x64.ActiveCfg = Release|Any CPU - {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|x64.Build.0 = Release|Any CPU - {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|x86.ActiveCfg = Release|Any CPU - {F276EB53-7758-4CEE-8013-61BA537B94C6}.Release|x86.Build.0 = Release|Any CPU {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|x64.ActiveCfg = Debug|Any CPU - {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|x64.Build.0 = Debug|Any CPU - {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|x86.ActiveCfg = Debug|Any CPU - {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Debug|x86.Build.0 = Debug|Any CPU {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|Any CPU.ActiveCfg = Release|Any CPU {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|Any CPU.Build.0 = Release|Any CPU - {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|x64.ActiveCfg = Release|Any CPU - {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|x64.Build.0 = Release|Any CPU - {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|x86.ActiveCfg = Release|Any CPU - {C17F7C35-4CAD-408F-9031-44EF1A3CC379}.Release|x86.Build.0 = Release|Any CPU {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|x64.ActiveCfg = Debug|Any CPU - {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|x64.Build.0 = Debug|Any CPU - {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|x86.ActiveCfg = Debug|Any CPU - {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Debug|x86.Build.0 = Debug|Any CPU {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|Any CPU.Build.0 = Release|Any CPU - {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|x64.ActiveCfg = Release|Any CPU - {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|x64.Build.0 = Release|Any CPU - {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|x86.ActiveCfg = Release|Any CPU - {337B29EB-54B4-43BF-8ADC-A5B3D11538AF}.Release|x86.Build.0 = Release|Any CPU {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|x64.ActiveCfg = Debug|Any CPU - {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|x64.Build.0 = Debug|Any CPU - {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|x86.ActiveCfg = Debug|Any CPU - {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Debug|x86.Build.0 = Debug|Any CPU {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|Any CPU.Build.0 = Release|Any CPU - {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|x64.ActiveCfg = Release|Any CPU - {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|x64.Build.0 = Release|Any CPU - {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|x86.ActiveCfg = Release|Any CPU - {92406BD8-0270-4BF9-B860-D1AE6508DF8C}.Release|x86.Build.0 = Release|Any CPU {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|x64.ActiveCfg = Debug|Any CPU - {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|x64.Build.0 = Debug|Any CPU - {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|x86.ActiveCfg = Debug|Any CPU - {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Debug|x86.Build.0 = Debug|Any CPU {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|Any CPU.Build.0 = Release|Any CPU - {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|x64.ActiveCfg = Release|Any CPU - {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|x64.Build.0 = Release|Any CPU - {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|x86.ActiveCfg = Release|Any CPU - {15B9B8C8-6935-423A-9328-E5D5E48F26AA}.Release|x86.Build.0 = Release|Any CPU {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|x64.ActiveCfg = Debug|Any CPU - {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|x64.Build.0 = Debug|Any CPU - {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|x86.ActiveCfg = Debug|Any CPU - {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Debug|x86.Build.0 = Debug|Any CPU {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|Any CPU.Build.0 = Release|Any CPU - {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|x64.ActiveCfg = Release|Any CPU - {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|x64.Build.0 = Release|Any CPU - {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|x86.ActiveCfg = Release|Any CPU - {2E69E8B9-6D16-4B81-B9A7-9EB106A75D8C}.Release|x86.Build.0 = Release|Any CPU {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|x64.ActiveCfg = Debug|Any CPU - {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|x64.Build.0 = Debug|Any CPU - {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|x86.ActiveCfg = Debug|Any CPU - {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Debug|x86.Build.0 = Debug|Any CPU {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|Any CPU.Build.0 = Release|Any CPU - {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|x64.ActiveCfg = Release|Any CPU - {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|x64.Build.0 = Release|Any CPU - {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|x86.ActiveCfg = Release|Any CPU - {4B03B049-8D29-4C8A-BA00-88B93D3C0BC1}.Release|x86.Build.0 = Release|Any CPU {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|x64.ActiveCfg = Debug|Any CPU - {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|x64.Build.0 = Debug|Any CPU - {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|x86.ActiveCfg = Debug|Any CPU - {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Debug|x86.Build.0 = Debug|Any CPU {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|Any CPU.ActiveCfg = Release|Any CPU {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|Any CPU.Build.0 = Release|Any CPU - {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|x64.ActiveCfg = Release|Any CPU - {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|x64.Build.0 = Release|Any CPU - {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|x86.ActiveCfg = Release|Any CPU - {9AC900E8-6D03-412B-8D9B-302FD0FBD059}.Release|x86.Build.0 = Release|Any CPU {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|x64.ActiveCfg = Debug|Any CPU - {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|x64.Build.0 = Debug|Any CPU - {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|x86.ActiveCfg = Debug|Any CPU - {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Debug|x86.Build.0 = Debug|Any CPU {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|Any CPU.ActiveCfg = Release|Any CPU {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|Any CPU.Build.0 = Release|Any CPU - {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|x64.ActiveCfg = Release|Any CPU - {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|x64.Build.0 = Release|Any CPU - {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|x86.ActiveCfg = Release|Any CPU - {C66A6EDB-D29A-49EE-841E-75F239DE5A04}.Release|x86.Build.0 = Release|Any CPU {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|Any CPU.Build.0 = Debug|Any CPU - {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|x64.ActiveCfg = Debug|Any CPU - {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|x64.Build.0 = Debug|Any CPU - {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|x86.ActiveCfg = Debug|Any CPU - {86614CB9-0768-40BF-8C27-699E3990B733}.Debug|x86.Build.0 = Debug|Any CPU {86614CB9-0768-40BF-8C27-699E3990B733}.Release|Any CPU.ActiveCfg = Release|Any CPU {86614CB9-0768-40BF-8C27-699E3990B733}.Release|Any CPU.Build.0 = Release|Any CPU - {86614CB9-0768-40BF-8C27-699E3990B733}.Release|x64.ActiveCfg = Release|Any CPU - {86614CB9-0768-40BF-8C27-699E3990B733}.Release|x64.Build.0 = Release|Any CPU - {86614CB9-0768-40BF-8C27-699E3990B733}.Release|x86.ActiveCfg = Release|Any CPU - {86614CB9-0768-40BF-8C27-699E3990B733}.Release|x86.Build.0 = Release|Any CPU {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|x64.ActiveCfg = Debug|Any CPU - {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|x64.Build.0 = Debug|Any CPU - {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|x86.ActiveCfg = Debug|Any CPU - {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|x86.Build.0 = Debug|Any CPU {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|Any CPU.Build.0 = Release|Any CPU - {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|x64.ActiveCfg = Release|Any CPU - {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|x64.Build.0 = Release|Any CPU - {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|x86.ActiveCfg = Release|Any CPU - {ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|x86.Build.0 = Release|Any CPU {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|x64.ActiveCfg = Debug|Any CPU - {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|x64.Build.0 = Debug|Any CPU - {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|x86.ActiveCfg = Debug|Any CPU - {D50C810D-7B5E-4332-A0FF-765101B09B23}.Debug|x86.Build.0 = Debug|Any CPU {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|Any CPU.ActiveCfg = Release|Any CPU {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|Any CPU.Build.0 = Release|Any CPU - {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|x64.ActiveCfg = Release|Any CPU - {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|x64.Build.0 = Release|Any CPU - {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|x86.ActiveCfg = Release|Any CPU - {D50C810D-7B5E-4332-A0FF-765101B09B23}.Release|x86.Build.0 = Release|Any CPU {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|x64.ActiveCfg = Debug|Any CPU - {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|x64.Build.0 = Debug|Any CPU - {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|x86.ActiveCfg = Debug|Any CPU - {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|x86.Build.0 = Debug|Any CPU {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|Any CPU.Build.0 = Release|Any CPU - {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|x64.ActiveCfg = Release|Any CPU - {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|x64.Build.0 = Release|Any CPU - {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|x86.ActiveCfg = Release|Any CPU - {7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|x86.Build.0 = Release|Any CPU {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|Any CPU.Build.0 = Debug|Any CPU - {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|x64.ActiveCfg = Debug|Any CPU - {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|x64.Build.0 = Debug|Any CPU - {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|x86.ActiveCfg = Debug|Any CPU - {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Debug|x86.Build.0 = Debug|Any CPU {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|Any CPU.ActiveCfg = Release|Any CPU {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|Any CPU.Build.0 = Release|Any CPU - {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|x64.ActiveCfg = Release|Any CPU - {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|x64.Build.0 = Release|Any CPU - {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|x86.ActiveCfg = Release|Any CPU - {842A4AA5-A82B-468E-9C25-BCD63C9C8E48}.Release|x86.Build.0 = Release|Any CPU {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|x64.ActiveCfg = Debug|Any CPU - {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|x64.Build.0 = Debug|Any CPU - {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|x86.ActiveCfg = Debug|Any CPU - {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Debug|x86.Build.0 = Debug|Any CPU {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|Any CPU.Build.0 = Release|Any CPU - {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|x64.ActiveCfg = Release|Any CPU - {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|x64.Build.0 = Release|Any CPU - {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|x86.ActiveCfg = Release|Any CPU - {D7E61F39-7503-4ECA-9A86-43F04D4DBE7D}.Release|x86.Build.0 = Release|Any CPU {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|x64.ActiveCfg = Debug|Any CPU - {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|x64.Build.0 = Debug|Any CPU - {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|x86.ActiveCfg = Debug|Any CPU - {20A668AB-CFBB-44B0-B76B-42705625E06D}.Debug|x86.Build.0 = Debug|Any CPU {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|Any CPU.ActiveCfg = Release|Any CPU {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|Any CPU.Build.0 = Release|Any CPU - {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|x64.ActiveCfg = Release|Any CPU - {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|x64.Build.0 = Release|Any CPU - {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|x86.ActiveCfg = Release|Any CPU - {20A668AB-CFBB-44B0-B76B-42705625E06D}.Release|x86.Build.0 = Release|Any CPU {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|x64.ActiveCfg = Debug|Any CPU - {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|x64.Build.0 = Debug|Any CPU - {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|x86.ActiveCfg = Debug|Any CPU - {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Debug|x86.Build.0 = Debug|Any CPU {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|Any CPU.Build.0 = Release|Any CPU - {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|x64.ActiveCfg = Release|Any CPU - {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|x64.Build.0 = Release|Any CPU - {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|x86.ActiveCfg = Release|Any CPU - {CC1E9908-075D-4138-852A-6EA3B5F32E88}.Release|x86.Build.0 = Release|Any CPU {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|x64.ActiveCfg = Debug|Any CPU - {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|x64.Build.0 = Debug|Any CPU - {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|x86.ActiveCfg = Debug|Any CPU - {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Debug|x86.Build.0 = Debug|Any CPU {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|Any CPU.ActiveCfg = Release|Any CPU {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|Any CPU.Build.0 = Release|Any CPU - {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|x64.ActiveCfg = Release|Any CPU - {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|x64.Build.0 = Release|Any CPU - {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|x86.ActiveCfg = Release|Any CPU - {27DB335F-5012-4276-98C5-EAEA335C8C7B}.Release|x86.Build.0 = Release|Any CPU {5325536E-8E3A-4611-AB92-B03369493354}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5325536E-8E3A-4611-AB92-B03369493354}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5325536E-8E3A-4611-AB92-B03369493354}.Debug|x64.ActiveCfg = Debug|Any CPU - {5325536E-8E3A-4611-AB92-B03369493354}.Debug|x64.Build.0 = Debug|Any CPU - {5325536E-8E3A-4611-AB92-B03369493354}.Debug|x86.ActiveCfg = Debug|Any CPU - {5325536E-8E3A-4611-AB92-B03369493354}.Debug|x86.Build.0 = Debug|Any CPU {5325536E-8E3A-4611-AB92-B03369493354}.Release|Any CPU.ActiveCfg = Release|Any CPU {5325536E-8E3A-4611-AB92-B03369493354}.Release|Any CPU.Build.0 = Release|Any CPU - {5325536E-8E3A-4611-AB92-B03369493354}.Release|x64.ActiveCfg = Release|Any CPU - {5325536E-8E3A-4611-AB92-B03369493354}.Release|x64.Build.0 = Release|Any CPU - {5325536E-8E3A-4611-AB92-B03369493354}.Release|x86.ActiveCfg = Release|Any CPU - {5325536E-8E3A-4611-AB92-B03369493354}.Release|x86.Build.0 = Release|Any CPU {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|x64.ActiveCfg = Debug|Any CPU - {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|x64.Build.0 = Debug|Any CPU - {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|x86.ActiveCfg = Debug|Any CPU - {92AE059B-928C-404F-BFC6-9CA57712AB90}.Debug|x86.Build.0 = Debug|Any CPU {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|Any CPU.ActiveCfg = Release|Any CPU {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|Any CPU.Build.0 = Release|Any CPU - {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|x64.ActiveCfg = Release|Any CPU - {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|x64.Build.0 = Release|Any CPU - {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|x86.ActiveCfg = Release|Any CPU - {92AE059B-928C-404F-BFC6-9CA57712AB90}.Release|x86.Build.0 = Release|Any CPU {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|x64.ActiveCfg = Debug|Any CPU - {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|x64.Build.0 = Debug|Any CPU - {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|x86.ActiveCfg = Debug|Any CPU - {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Debug|x86.Build.0 = Debug|Any CPU {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|Any CPU.ActiveCfg = Release|Any CPU {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|Any CPU.Build.0 = Release|Any CPU - {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|x64.ActiveCfg = Release|Any CPU - {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|x64.Build.0 = Release|Any CPU - {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|x86.ActiveCfg = Release|Any CPU - {112F6B50-0FD3-45AA-992E-72D7B873BF00}.Release|x86.Build.0 = Release|Any CPU {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|x64.ActiveCfg = Debug|Any CPU - {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|x64.Build.0 = Debug|Any CPU - {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|x86.ActiveCfg = Debug|Any CPU - {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Debug|x86.Build.0 = Debug|Any CPU {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|Any CPU.ActiveCfg = Release|Any CPU {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|Any CPU.Build.0 = Release|Any CPU - {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|x64.ActiveCfg = Release|Any CPU - {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|x64.Build.0 = Release|Any CPU - {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|x86.ActiveCfg = Release|Any CPU - {140A6CAD-FC84-4A09-A939-8A5692DF0C7C}.Release|x86.Build.0 = Release|Any CPU {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|x64.ActiveCfg = Debug|Any CPU - {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|x64.Build.0 = Debug|Any CPU - {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|x86.ActiveCfg = Debug|Any CPU - {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Debug|x86.Build.0 = Debug|Any CPU {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|Any CPU.ActiveCfg = Release|Any CPU {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|Any CPU.Build.0 = Release|Any CPU - {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|x64.ActiveCfg = Release|Any CPU - {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|x64.Build.0 = Release|Any CPU - {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|x86.ActiveCfg = Release|Any CPU - {FACB1C2F-3BF7-4DD3-958B-E471E69E214A}.Release|x86.Build.0 = Release|Any CPU {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|x64.ActiveCfg = Debug|Any CPU - {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|x64.Build.0 = Debug|Any CPU - {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|x86.ActiveCfg = Debug|Any CPU - {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Debug|x86.Build.0 = Debug|Any CPU {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|Any CPU.Build.0 = Release|Any CPU - {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|x64.ActiveCfg = Release|Any CPU - {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|x64.Build.0 = Release|Any CPU - {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|x86.ActiveCfg = Release|Any CPU - {DC80AFEC-EFB4-4B21-BF79-44DEA6E1FE26}.Release|x86.Build.0 = Release|Any CPU {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|x64.ActiveCfg = Debug|Any CPU - {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|x64.Build.0 = Debug|Any CPU - {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|x86.ActiveCfg = Debug|Any CPU - {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Debug|x86.Build.0 = Debug|Any CPU {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|Any CPU.ActiveCfg = Release|Any CPU {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|Any CPU.Build.0 = Release|Any CPU - {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|x64.ActiveCfg = Release|Any CPU - {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|x64.Build.0 = Release|Any CPU - {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|x86.ActiveCfg = Release|Any CPU - {82AA3C52-9B98-4203-9D26-1FA6E5BAEEBD}.Release|x86.Build.0 = Release|Any CPU {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|x64.ActiveCfg = Debug|Any CPU - {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|x64.Build.0 = Debug|Any CPU - {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|x86.ActiveCfg = Debug|Any CPU - {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Debug|x86.Build.0 = Debug|Any CPU {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|Any CPU.Build.0 = Release|Any CPU - {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|x64.ActiveCfg = Release|Any CPU - {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|x64.Build.0 = Release|Any CPU - {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|x86.ActiveCfg = Release|Any CPU - {0D9EB03D-99AF-4A80-B7CE-2302A8D3747B}.Release|x86.Build.0 = Release|Any CPU {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|x64.ActiveCfg = Debug|Any CPU - {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|x64.Build.0 = Debug|Any CPU - {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|x86.ActiveCfg = Debug|Any CPU - {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Debug|x86.Build.0 = Debug|Any CPU {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|Any CPU.Build.0 = Release|Any CPU - {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|x64.ActiveCfg = Release|Any CPU - {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|x64.Build.0 = Release|Any CPU - {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|x86.ActiveCfg = Release|Any CPU - {E82424B3-0E73-4954-B6A6-BFF1029A08DE}.Release|x86.Build.0 = Release|Any CPU {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|x64.ActiveCfg = Debug|Any CPU - {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|x64.Build.0 = Debug|Any CPU - {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|x86.ActiveCfg = Debug|Any CPU - {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Debug|x86.Build.0 = Debug|Any CPU {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|Any CPU.ActiveCfg = Release|Any CPU {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|Any CPU.Build.0 = Release|Any CPU - {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|x64.ActiveCfg = Release|Any CPU - {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|x64.Build.0 = Release|Any CPU - {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|x86.ActiveCfg = Release|Any CPU - {42306484-B2BF-4B52-B950-E0CDFA58B02A}.Release|x86.Build.0 = Release|Any CPU {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|x64.ActiveCfg = Debug|Any CPU - {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|x64.Build.0 = Debug|Any CPU - {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|x86.ActiveCfg = Debug|Any CPU - {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Debug|x86.Build.0 = Debug|Any CPU {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|Any CPU.ActiveCfg = Release|Any CPU {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|Any CPU.Build.0 = Release|Any CPU - {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|x64.ActiveCfg = Release|Any CPU - {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|x64.Build.0 = Release|Any CPU - {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|x86.ActiveCfg = Release|Any CPU - {286D1FB0-716C-47CB-A519-22E1D34DFDF7}.Release|x86.Build.0 = Release|Any CPU {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|x64.ActiveCfg = Debug|Any CPU - {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|x64.Build.0 = Debug|Any CPU - {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|x86.ActiveCfg = Debug|Any CPU - {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Debug|x86.Build.0 = Debug|Any CPU {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|Any CPU.Build.0 = Release|Any CPU - {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|x64.ActiveCfg = Release|Any CPU - {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|x64.Build.0 = Release|Any CPU - {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|x86.ActiveCfg = Release|Any CPU - {F8A4A5C4-86D9-4CF2-A2FC-3B46CCC51750}.Release|x86.Build.0 = Release|Any CPU {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|x64.ActiveCfg = Debug|Any CPU - {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|x64.Build.0 = Debug|Any CPU - {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|x86.ActiveCfg = Debug|Any CPU - {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Debug|x86.Build.0 = Debug|Any CPU {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|Any CPU.Build.0 = Release|Any CPU - {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|x64.ActiveCfg = Release|Any CPU - {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|x64.Build.0 = Release|Any CPU - {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|x86.ActiveCfg = Release|Any CPU - {1B19314C-9F33-4E71-AF0C-46ED8AB621CE}.Release|x86.Build.0 = Release|Any CPU {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|x64.ActiveCfg = Debug|Any CPU - {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|x64.Build.0 = Debug|Any CPU - {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|x86.ActiveCfg = Debug|Any CPU - {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Debug|x86.Build.0 = Debug|Any CPU {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|Any CPU.ActiveCfg = Release|Any CPU {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|Any CPU.Build.0 = Release|Any CPU - {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|x64.ActiveCfg = Release|Any CPU - {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|x64.Build.0 = Release|Any CPU - {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|x86.ActiveCfg = Release|Any CPU - {26DDE1D4-45D5-4CAA-ACB6-71FECF781B27}.Release|x86.Build.0 = Release|Any CPU {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|x64.ActiveCfg = Debug|Any CPU - {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|x64.Build.0 = Debug|Any CPU - {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|x86.ActiveCfg = Debug|Any CPU - {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Debug|x86.Build.0 = Debug|Any CPU {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|Any CPU.Build.0 = Release|Any CPU - {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|x64.ActiveCfg = Release|Any CPU - {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|x64.Build.0 = Release|Any CPU - {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|x86.ActiveCfg = Release|Any CPU - {DDF7546B-51C4-46EB-B102-7EE720E8E74F}.Release|x86.Build.0 = Release|Any CPU {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|Any CPU.Build.0 = Debug|Any CPU - {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|x64.ActiveCfg = Debug|Any CPU - {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|x64.Build.0 = Debug|Any CPU - {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|x86.ActiveCfg = Debug|Any CPU - {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Debug|x86.Build.0 = Debug|Any CPU {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|Any CPU.ActiveCfg = Release|Any CPU {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|Any CPU.Build.0 = Release|Any CPU - {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|x64.ActiveCfg = Release|Any CPU - {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|x64.Build.0 = Release|Any CPU - {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|x86.ActiveCfg = Release|Any CPU - {61AB67B0-0F4A-47A2-A4D8-9738AA34C468}.Release|x86.Build.0 = Release|Any CPU {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|x64.ActiveCfg = Debug|Any CPU - {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|x64.Build.0 = Debug|Any CPU - {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|x86.ActiveCfg = Debug|Any CPU - {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Debug|x86.Build.0 = Debug|Any CPU {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|Any CPU.Build.0 = Release|Any CPU - {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|x64.ActiveCfg = Release|Any CPU - {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|x64.Build.0 = Release|Any CPU - {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|x86.ActiveCfg = Release|Any CPU - {6A2FEBAB-2372-4043-BD37-B2E94887BA9B}.Release|x86.Build.0 = Release|Any CPU {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|x64.ActiveCfg = Debug|Any CPU - {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|x64.Build.0 = Debug|Any CPU - {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|x86.ActiveCfg = Debug|Any CPU - {1745B11F-F700-4604-B61F-54B962A6BE9A}.Debug|x86.Build.0 = Debug|Any CPU {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|Any CPU.Build.0 = Release|Any CPU - {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|x64.ActiveCfg = Release|Any CPU - {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|x64.Build.0 = Release|Any CPU - {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|x86.ActiveCfg = Release|Any CPU - {1745B11F-F700-4604-B61F-54B962A6BE9A}.Release|x86.Build.0 = Release|Any CPU {839267B9-492D-47B6-A6AF-454282142123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {839267B9-492D-47B6-A6AF-454282142123}.Debug|Any CPU.Build.0 = Debug|Any CPU - {839267B9-492D-47B6-A6AF-454282142123}.Debug|x64.ActiveCfg = Debug|Any CPU - {839267B9-492D-47B6-A6AF-454282142123}.Debug|x64.Build.0 = Debug|Any CPU - {839267B9-492D-47B6-A6AF-454282142123}.Debug|x86.ActiveCfg = Debug|Any CPU - {839267B9-492D-47B6-A6AF-454282142123}.Debug|x86.Build.0 = Debug|Any CPU {839267B9-492D-47B6-A6AF-454282142123}.Release|Any CPU.ActiveCfg = Release|Any CPU {839267B9-492D-47B6-A6AF-454282142123}.Release|Any CPU.Build.0 = Release|Any CPU - {839267B9-492D-47B6-A6AF-454282142123}.Release|x64.ActiveCfg = Release|Any CPU - {839267B9-492D-47B6-A6AF-454282142123}.Release|x64.Build.0 = Release|Any CPU - {839267B9-492D-47B6-A6AF-454282142123}.Release|x86.ActiveCfg = Release|Any CPU - {839267B9-492D-47B6-A6AF-454282142123}.Release|x86.Build.0 = Release|Any CPU {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|x64.ActiveCfg = Debug|Any CPU - {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|x64.Build.0 = Debug|Any CPU - {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|x86.ActiveCfg = Debug|Any CPU - {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Debug|x86.Build.0 = Debug|Any CPU {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|Any CPU.ActiveCfg = Release|Any CPU {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|Any CPU.Build.0 = Release|Any CPU - {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|x64.ActiveCfg = Release|Any CPU - {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|x64.Build.0 = Release|Any CPU - {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|x86.ActiveCfg = Release|Any CPU - {514D2D1E-E72F-4998-9DF3-D841F399AB37}.Release|x86.Build.0 = Release|Any CPU {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|x64.ActiveCfg = Debug|Any CPU - {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|x64.Build.0 = Debug|Any CPU - {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|x86.ActiveCfg = Debug|Any CPU - {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Debug|x86.Build.0 = Debug|Any CPU {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|Any CPU.Build.0 = Release|Any CPU - {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|x64.ActiveCfg = Release|Any CPU - {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|x64.Build.0 = Release|Any CPU - {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|x86.ActiveCfg = Release|Any CPU - {01C2AC98-B615-49F8-80A5-475A2B75A8FD}.Release|x86.Build.0 = Release|Any CPU {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|x64.ActiveCfg = Debug|Any CPU - {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|x64.Build.0 = Debug|Any CPU - {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|x86.ActiveCfg = Debug|Any CPU - {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Debug|x86.Build.0 = Debug|Any CPU {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|Any CPU.Build.0 = Release|Any CPU - {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|x64.ActiveCfg = Release|Any CPU - {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|x64.Build.0 = Release|Any CPU - {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|x86.ActiveCfg = Release|Any CPU - {C40ED377-E365-45FB-8B42-27BE82CC7BE3}.Release|x86.Build.0 = Release|Any CPU {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|x64.ActiveCfg = Debug|Any CPU - {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|x64.Build.0 = Debug|Any CPU - {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|x86.ActiveCfg = Debug|Any CPU - {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Debug|x86.Build.0 = Debug|Any CPU {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|Any CPU.Build.0 = Release|Any CPU - {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|x64.ActiveCfg = Release|Any CPU - {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|x64.Build.0 = Release|Any CPU - {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|x86.ActiveCfg = Release|Any CPU - {DF2786DF-234D-4A8C-B166-0B8F8B7D527B}.Release|x86.Build.0 = Release|Any CPU {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|x64.ActiveCfg = Debug|Any CPU - {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|x64.Build.0 = Debug|Any CPU - {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|x86.ActiveCfg = Debug|Any CPU - {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Debug|x86.Build.0 = Debug|Any CPU {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|Any CPU.Build.0 = Release|Any CPU - {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|x64.ActiveCfg = Release|Any CPU - {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|x64.Build.0 = Release|Any CPU - {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|x86.ActiveCfg = Release|Any CPU - {7A2EC21F-D411-4B45-AA3B-70143C1A9E2F}.Release|x86.Build.0 = Release|Any CPU {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|x64.ActiveCfg = Debug|Any CPU - {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|x64.Build.0 = Debug|Any CPU - {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|x86.ActiveCfg = Debug|Any CPU - {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Debug|x86.Build.0 = Debug|Any CPU {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|Any CPU.ActiveCfg = Release|Any CPU {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|Any CPU.Build.0 = Release|Any CPU - {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|x64.ActiveCfg = Release|Any CPU - {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|x64.Build.0 = Release|Any CPU - {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|x86.ActiveCfg = Release|Any CPU - {91BB9A49-9C88-4132-96E4-2D6148ACDE77}.Release|x86.Build.0 = Release|Any CPU {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|x64.ActiveCfg = Debug|Any CPU - {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|x64.Build.0 = Debug|Any CPU - {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|x86.ActiveCfg = Debug|Any CPU - {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|x86.Build.0 = Debug|Any CPU {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|Any CPU.ActiveCfg = Release|Any CPU {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|Any CPU.Build.0 = Release|Any CPU - {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|x64.ActiveCfg = Release|Any CPU - {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|x64.Build.0 = Release|Any CPU - {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|x86.ActiveCfg = Release|Any CPU - {C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|x86.Build.0 = Release|Any CPU {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|x64.ActiveCfg = Debug|Any CPU - {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|x64.Build.0 = Debug|Any CPU - {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|x86.ActiveCfg = Debug|Any CPU - {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|x86.Build.0 = Debug|Any CPU {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|Any CPU.Build.0 = Release|Any CPU - {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|x64.ActiveCfg = Release|Any CPU - {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|x64.Build.0 = Release|Any CPU - {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|x86.ActiveCfg = Release|Any CPU - {815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|x86.Build.0 = Release|Any CPU {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|Any CPU.Build.0 = Debug|Any CPU - {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|x64.ActiveCfg = Debug|Any CPU - {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|x64.Build.0 = Debug|Any CPU - {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|x86.ActiveCfg = Debug|Any CPU - {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Debug|x86.Build.0 = Debug|Any CPU {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|Any CPU.ActiveCfg = Release|Any CPU {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|Any CPU.Build.0 = Release|Any CPU - {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|x64.ActiveCfg = Release|Any CPU - {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|x64.Build.0 = Release|Any CPU - {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|x86.ActiveCfg = Release|Any CPU - {574A52D9-E7A5-4E11-8F36-5C8AA7033287}.Release|x86.Build.0 = Release|Any CPU {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|x64.ActiveCfg = Debug|Any CPU - {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|x64.Build.0 = Debug|Any CPU - {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|x86.ActiveCfg = Debug|Any CPU - {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Debug|x86.Build.0 = Debug|Any CPU {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|Any CPU.Build.0 = Release|Any CPU - {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|x64.ActiveCfg = Release|Any CPU - {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|x64.Build.0 = Release|Any CPU - {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|x86.ActiveCfg = Release|Any CPU - {57633BE6-C7AD-4197-A75A-F38A2312A4D9}.Release|x86.Build.0 = Release|Any CPU {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|x64.ActiveCfg = Debug|Any CPU - {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|x64.Build.0 = Debug|Any CPU - {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|x86.ActiveCfg = Debug|Any CPU - {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Debug|x86.Build.0 = Debug|Any CPU {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|Any CPU.Build.0 = Release|Any CPU - {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|x64.ActiveCfg = Release|Any CPU - {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|x64.Build.0 = Release|Any CPU - {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|x86.ActiveCfg = Release|Any CPU - {DC804C6D-3774-4FA7-8EA6-8C8198995BD6}.Release|x86.Build.0 = Release|Any CPU {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|x64.ActiveCfg = Debug|Any CPU - {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|x64.Build.0 = Debug|Any CPU - {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|x86.ActiveCfg = Debug|Any CPU - {82314BA9-8AB2-46B9-AB12-9109619B8331}.Debug|x86.Build.0 = Debug|Any CPU {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|Any CPU.ActiveCfg = Release|Any CPU {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|Any CPU.Build.0 = Release|Any CPU - {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|x64.ActiveCfg = Release|Any CPU - {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|x64.Build.0 = Release|Any CPU - {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|x86.ActiveCfg = Release|Any CPU - {82314BA9-8AB2-46B9-AB12-9109619B8331}.Release|x86.Build.0 = Release|Any CPU {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|x64.ActiveCfg = Debug|Any CPU - {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|x64.Build.0 = Debug|Any CPU - {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|x86.ActiveCfg = Debug|Any CPU - {1F869094-FAC8-49B5-92A4-706C028CDF93}.Debug|x86.Build.0 = Debug|Any CPU {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|Any CPU.Build.0 = Release|Any CPU - {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|x64.ActiveCfg = Release|Any CPU - {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|x64.Build.0 = Release|Any CPU - {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|x86.ActiveCfg = Release|Any CPU - {1F869094-FAC8-49B5-92A4-706C028CDF93}.Release|x86.Build.0 = Release|Any CPU {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|x64.ActiveCfg = Debug|Any CPU - {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|x64.Build.0 = Debug|Any CPU - {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|x86.ActiveCfg = Debug|Any CPU - {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Debug|x86.Build.0 = Debug|Any CPU {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|Any CPU.ActiveCfg = Release|Any CPU {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|Any CPU.Build.0 = Release|Any CPU - {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|x64.ActiveCfg = Release|Any CPU - {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|x64.Build.0 = Release|Any CPU - {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|x86.ActiveCfg = Release|Any CPU - {FAE6E4C3-5A0D-45A5-9D17-57F4F6DA2593}.Release|x86.Build.0 = Release|Any CPU {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|x64.ActiveCfg = Debug|Any CPU - {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|x64.Build.0 = Debug|Any CPU - {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|x86.ActiveCfg = Debug|Any CPU - {289A91E9-81A9-422D-9CCD-12819081A29A}.Debug|x86.Build.0 = Debug|Any CPU {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|Any CPU.ActiveCfg = Release|Any CPU {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|Any CPU.Build.0 = Release|Any CPU - {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|x64.ActiveCfg = Release|Any CPU - {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|x64.Build.0 = Release|Any CPU - {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|x86.ActiveCfg = Release|Any CPU - {289A91E9-81A9-422D-9CCD-12819081A29A}.Release|x86.Build.0 = Release|Any CPU {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|x64.ActiveCfg = Debug|Any CPU - {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|x64.Build.0 = Debug|Any CPU - {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|x86.ActiveCfg = Debug|Any CPU - {23E576EB-6514-4617-8F04-FE7D5540136D}.Debug|x86.Build.0 = Debug|Any CPU {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|Any CPU.ActiveCfg = Release|Any CPU {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|Any CPU.Build.0 = Release|Any CPU - {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|x64.ActiveCfg = Release|Any CPU - {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|x64.Build.0 = Release|Any CPU - {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|x86.ActiveCfg = Release|Any CPU - {23E576EB-6514-4617-8F04-FE7D5540136D}.Release|x86.Build.0 = Release|Any CPU {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|x64.ActiveCfg = Debug|Any CPU - {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|x64.Build.0 = Debug|Any CPU - {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|x86.ActiveCfg = Debug|Any CPU - {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Debug|x86.Build.0 = Debug|Any CPU {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|Any CPU.ActiveCfg = Release|Any CPU {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|Any CPU.Build.0 = Release|Any CPU - {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|x64.ActiveCfg = Release|Any CPU - {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|x64.Build.0 = Release|Any CPU - {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|x86.ActiveCfg = Release|Any CPU - {ECD22287-9B9F-489A-84A7-E66D65A39D73}.Release|x86.Build.0 = Release|Any CPU {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|x64.ActiveCfg = Debug|Any CPU - {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|x64.Build.0 = Debug|Any CPU - {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|x86.ActiveCfg = Debug|Any CPU - {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Debug|x86.Build.0 = Debug|Any CPU {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|Any CPU.Build.0 = Release|Any CPU - {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|x64.ActiveCfg = Release|Any CPU - {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|x64.Build.0 = Release|Any CPU - {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|x86.ActiveCfg = Release|Any CPU - {B8F9B052-84BF-436C-B22B-CEBD5EB1F8E3}.Release|x86.Build.0 = Release|Any CPU {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|x64.ActiveCfg = Debug|Any CPU - {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|x64.Build.0 = Debug|Any CPU - {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|x86.ActiveCfg = Debug|Any CPU - {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Debug|x86.Build.0 = Debug|Any CPU {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|Any CPU.Build.0 = Release|Any CPU - {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|x64.ActiveCfg = Release|Any CPU - {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|x64.Build.0 = Release|Any CPU - {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|x86.ActiveCfg = Release|Any CPU - {8C7A98A6-5F61-492B-980D-0A9F5F9F5C73}.Release|x86.Build.0 = Release|Any CPU {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|x64.ActiveCfg = Debug|Any CPU - {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|x64.Build.0 = Debug|Any CPU - {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|x86.ActiveCfg = Debug|Any CPU - {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Debug|x86.Build.0 = Debug|Any CPU {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|Any CPU.Build.0 = Release|Any CPU - {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|x64.ActiveCfg = Release|Any CPU - {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|x64.Build.0 = Release|Any CPU - {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|x86.ActiveCfg = Release|Any CPU - {8E42EF81-A630-4BDB-B642-3F20C863F9BE}.Release|x86.Build.0 = Release|Any CPU {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|x64.ActiveCfg = Debug|Any CPU - {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|x64.Build.0 = Debug|Any CPU - {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|x86.ActiveCfg = Debug|Any CPU - {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Debug|x86.Build.0 = Debug|Any CPU {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|Any CPU.ActiveCfg = Release|Any CPU {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|Any CPU.Build.0 = Release|Any CPU - {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|x64.ActiveCfg = Release|Any CPU - {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|x64.Build.0 = Release|Any CPU - {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|x86.ActiveCfg = Release|Any CPU - {68862DC5-65B7-4517-B909-AB334F9FCF6E}.Release|x86.Build.0 = Release|Any CPU {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|x64.ActiveCfg = Debug|Any CPU - {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|x64.Build.0 = Debug|Any CPU - {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|x86.ActiveCfg = Debug|Any CPU - {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Debug|x86.Build.0 = Debug|Any CPU {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|Any CPU.Build.0 = Release|Any CPU - {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|x64.ActiveCfg = Release|Any CPU - {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|x64.Build.0 = Release|Any CPU - {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|x86.ActiveCfg = Release|Any CPU - {E3CF7FFC-56A0-4033-87A9-BB3080CF030E}.Release|x86.Build.0 = Release|Any CPU {101681FB-569F-4941-B943-2AD380039BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {101681FB-569F-4941-B943-2AD380039BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {101681FB-569F-4941-B943-2AD380039BE0}.Debug|x64.ActiveCfg = Debug|Any CPU - {101681FB-569F-4941-B943-2AD380039BE0}.Debug|x64.Build.0 = Debug|Any CPU - {101681FB-569F-4941-B943-2AD380039BE0}.Debug|x86.ActiveCfg = Debug|Any CPU - {101681FB-569F-4941-B943-2AD380039BE0}.Debug|x86.Build.0 = Debug|Any CPU {101681FB-569F-4941-B943-2AD380039BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU {101681FB-569F-4941-B943-2AD380039BE0}.Release|Any CPU.Build.0 = Release|Any CPU - {101681FB-569F-4941-B943-2AD380039BE0}.Release|x64.ActiveCfg = Release|Any CPU - {101681FB-569F-4941-B943-2AD380039BE0}.Release|x64.Build.0 = Release|Any CPU - {101681FB-569F-4941-B943-2AD380039BE0}.Release|x86.ActiveCfg = Release|Any CPU - {101681FB-569F-4941-B943-2AD380039BE0}.Release|x86.Build.0 = Release|Any CPU {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|x64.ActiveCfg = Debug|Any CPU - {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|x64.Build.0 = Debug|Any CPU - {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|x86.ActiveCfg = Debug|Any CPU - {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Debug|x86.Build.0 = Debug|Any CPU {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|Any CPU.Build.0 = Release|Any CPU - {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|x64.ActiveCfg = Release|Any CPU - {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|x64.Build.0 = Release|Any CPU - {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|x86.ActiveCfg = Release|Any CPU - {CF8C4235-6AE6-404E-B572-4FF4E85AB5FF}.Release|x86.Build.0 = Release|Any CPU {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|x64.ActiveCfg = Debug|Any CPU - {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|x64.Build.0 = Debug|Any CPU - {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|x86.ActiveCfg = Debug|Any CPU - {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Debug|x86.Build.0 = Debug|Any CPU {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|Any CPU.ActiveCfg = Release|Any CPU {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|Any CPU.Build.0 = Release|Any CPU - {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|x64.ActiveCfg = Release|Any CPU - {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|x64.Build.0 = Release|Any CPU - {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|x86.ActiveCfg = Release|Any CPU - {8AF5DDBE-2631-4E71-9045-73A6356CE86B}.Release|x86.Build.0 = Release|Any CPU {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|x64.ActiveCfg = Debug|Any CPU - {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|x64.Build.0 = Debug|Any CPU - {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|x86.ActiveCfg = Debug|Any CPU - {A4DFC94C-0769-41B3-926C-22642185C878}.Debug|x86.Build.0 = Debug|Any CPU {A4DFC94C-0769-41B3-926C-22642185C878}.Release|Any CPU.ActiveCfg = Release|Any CPU {A4DFC94C-0769-41B3-926C-22642185C878}.Release|Any CPU.Build.0 = Release|Any CPU - {A4DFC94C-0769-41B3-926C-22642185C878}.Release|x64.ActiveCfg = Release|Any CPU - {A4DFC94C-0769-41B3-926C-22642185C878}.Release|x64.Build.0 = Release|Any CPU - {A4DFC94C-0769-41B3-926C-22642185C878}.Release|x86.ActiveCfg = Release|Any CPU - {A4DFC94C-0769-41B3-926C-22642185C878}.Release|x86.Build.0 = Release|Any CPU {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|Any CPU.Build.0 = Debug|Any CPU - {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|x64.ActiveCfg = Debug|Any CPU - {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|x64.Build.0 = Debug|Any CPU - {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|x86.ActiveCfg = Debug|Any CPU - {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Debug|x86.Build.0 = Debug|Any CPU {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|Any CPU.ActiveCfg = Release|Any CPU {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|Any CPU.Build.0 = Release|Any CPU - {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|x64.ActiveCfg = Release|Any CPU - {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|x64.Build.0 = Release|Any CPU - {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|x86.ActiveCfg = Release|Any CPU - {31333261-A9C2-4AEB-AA6C-AC66DB4FA966}.Release|x86.Build.0 = Release|Any CPU {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|x64.ActiveCfg = Debug|Any CPU - {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|x64.Build.0 = Debug|Any CPU - {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|x86.ActiveCfg = Debug|Any CPU - {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Debug|x86.Build.0 = Debug|Any CPU {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|Any CPU.Build.0 = Release|Any CPU - {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|x64.ActiveCfg = Release|Any CPU - {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|x64.Build.0 = Release|Any CPU - {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|x86.ActiveCfg = Release|Any CPU - {ADAC649F-A8CC-4CF2-8C34-288F7DEBBE69}.Release|x86.Build.0 = Release|Any CPU {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|x64.ActiveCfg = Debug|Any CPU - {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|x64.Build.0 = Debug|Any CPU - {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|x86.ActiveCfg = Debug|Any CPU - {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Debug|x86.Build.0 = Debug|Any CPU {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|Any CPU.Build.0 = Release|Any CPU - {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|x64.ActiveCfg = Release|Any CPU - {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|x64.Build.0 = Release|Any CPU - {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|x86.ActiveCfg = Release|Any CPU - {83C37AC5-51FB-47CD-8CBE-77AA114FF6F3}.Release|x86.Build.0 = Release|Any CPU {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|x64.ActiveCfg = Debug|Any CPU - {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|x64.Build.0 = Debug|Any CPU - {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|x86.ActiveCfg = Debug|Any CPU - {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Debug|x86.Build.0 = Debug|Any CPU {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|Any CPU.Build.0 = Release|Any CPU - {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|x64.ActiveCfg = Release|Any CPU - {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|x64.Build.0 = Release|Any CPU - {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|x86.ActiveCfg = Release|Any CPU - {55975423-C9C0-4C47-AD00-0F012F30AD3C}.Release|x86.Build.0 = Release|Any CPU {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|x64.ActiveCfg = Debug|Any CPU - {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|x64.Build.0 = Debug|Any CPU - {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|x86.ActiveCfg = Debug|Any CPU - {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Debug|x86.Build.0 = Debug|Any CPU {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|Any CPU.Build.0 = Release|Any CPU - {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|x64.ActiveCfg = Release|Any CPU - {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|x64.Build.0 = Release|Any CPU - {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|x86.ActiveCfg = Release|Any CPU - {4E96BD06-04CD-4014-BA42-10D2CDB820D6}.Release|x86.Build.0 = Release|Any CPU {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|x64.ActiveCfg = Debug|Any CPU - {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|x64.Build.0 = Debug|Any CPU - {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|x86.ActiveCfg = Debug|Any CPU - {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Debug|x86.Build.0 = Debug|Any CPU {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|Any CPU.Build.0 = Release|Any CPU - {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|x64.ActiveCfg = Release|Any CPU - {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|x64.Build.0 = Release|Any CPU - {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|x86.ActiveCfg = Release|Any CPU - {CD56ABE4-1CD2-4029-B556-E110A31A2CC4}.Release|x86.Build.0 = Release|Any CPU {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|x64.ActiveCfg = Debug|Any CPU - {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|x64.Build.0 = Debug|Any CPU - {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|x86.ActiveCfg = Debug|Any CPU - {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Debug|x86.Build.0 = Debug|Any CPU {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|Any CPU.Build.0 = Release|Any CPU - {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|x64.ActiveCfg = Release|Any CPU - {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|x64.Build.0 = Release|Any CPU - {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|x86.ActiveCfg = Release|Any CPU - {F3E62C24-5F82-4CF5-A994-0E10D04FB495}.Release|x86.Build.0 = Release|Any CPU {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|x64.ActiveCfg = Debug|Any CPU - {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|x64.Build.0 = Debug|Any CPU - {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|x86.ActiveCfg = Debug|Any CPU - {668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|x86.Build.0 = Debug|Any CPU {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|Any CPU.Build.0 = Release|Any CPU - {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|x64.ActiveCfg = Release|Any CPU - {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|x64.Build.0 = Release|Any CPU - {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|x86.ActiveCfg = Release|Any CPU - {668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|x86.Build.0 = Release|Any CPU {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|x64.ActiveCfg = Debug|Any CPU - {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|x64.Build.0 = Debug|Any CPU - {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|x86.ActiveCfg = Debug|Any CPU - {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|x86.Build.0 = Debug|Any CPU {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|Any CPU.Build.0 = Release|Any CPU - {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|x64.ActiveCfg = Release|Any CPU - {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|x64.Build.0 = Release|Any CPU - {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|x86.ActiveCfg = Release|Any CPU - {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|x86.Build.0 = Release|Any CPU {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|x64.ActiveCfg = Debug|Any CPU - {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|x64.Build.0 = Debug|Any CPU - {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|x86.ActiveCfg = Debug|Any CPU - {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|x86.Build.0 = Debug|Any CPU {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|Any CPU.Build.0 = Release|Any CPU - {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|x64.ActiveCfg = Release|Any CPU - {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|x64.Build.0 = Release|Any CPU - {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|x86.ActiveCfg = Release|Any CPU - {F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|x86.Build.0 = Release|Any CPU {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|x64.ActiveCfg = Debug|Any CPU - {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|x64.Build.0 = Debug|Any CPU - {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|x86.ActiveCfg = Debug|Any CPU - {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|x86.Build.0 = Debug|Any CPU {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|Any CPU.Build.0 = Release|Any CPU - {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|x64.ActiveCfg = Release|Any CPU - {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|x64.Build.0 = Release|Any CPU - {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|x86.ActiveCfg = Release|Any CPU - {7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|x86.Build.0 = Release|Any CPU {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|x64.ActiveCfg = Debug|Any CPU - {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|x64.Build.0 = Debug|Any CPU - {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|x86.ActiveCfg = Debug|Any CPU - {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Debug|x86.Build.0 = Debug|Any CPU {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|Any CPU.Build.0 = Release|Any CPU - {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|x64.ActiveCfg = Release|Any CPU - {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|x64.Build.0 = Release|Any CPU - {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|x86.ActiveCfg = Release|Any CPU - {B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}.Release|x86.Build.0 = Release|Any CPU {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|x64.ActiveCfg = Debug|Any CPU - {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|x64.Build.0 = Debug|Any CPU - {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|x86.ActiveCfg = Debug|Any CPU - {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Debug|x86.Build.0 = Debug|Any CPU {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|Any CPU.ActiveCfg = Release|Any CPU {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|Any CPU.Build.0 = Release|Any CPU - {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|x64.ActiveCfg = Release|Any CPU - {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|x64.Build.0 = Release|Any CPU - {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|x86.ActiveCfg = Release|Any CPU - {8350C405-9E17-4110-B9A8-0AB43A8816B7}.Release|x86.Build.0 = Release|Any CPU {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|x64.ActiveCfg = Debug|Any CPU - {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|x64.Build.0 = Debug|Any CPU - {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|x86.ActiveCfg = Debug|Any CPU - {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Debug|x86.Build.0 = Debug|Any CPU {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|Any CPU.Build.0 = Release|Any CPU - {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|x64.ActiveCfg = Release|Any CPU - {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|x64.Build.0 = Release|Any CPU - {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|x86.ActiveCfg = Release|Any CPU - {B1F6EA42-7B1B-469E-B304-6B2E6FE39852}.Release|x86.Build.0 = Release|Any CPU {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|x64.ActiveCfg = Debug|Any CPU - {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|x64.Build.0 = Debug|Any CPU - {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|x86.ActiveCfg = Debug|Any CPU - {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Debug|x86.Build.0 = Debug|Any CPU {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|Any CPU.Build.0 = Release|Any CPU - {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|x64.ActiveCfg = Release|Any CPU - {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|x64.Build.0 = Release|Any CPU - {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|x86.ActiveCfg = Release|Any CPU - {53B5B8F0-023E-4D2D-84F0-5B68610682A4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From c978f0c502885194c050f5d508cfabd2e615d31f Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Sat, 8 Nov 2025 00:59:03 +0100 Subject: [PATCH 26/32] chore: fix test builds - openapi instead of just api - reverted formated string in bundle.json --- .../EtsyAuthenticationOptions.cs | 2 +- .../Etsy/EtsyAuthenticationOptionsTests.cs | 17 ++++++++++++++++- .../Etsy/bundle.json | 6 +++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index ee43b2cae..a1adb5dac 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -89,7 +89,7 @@ public override void Validate() if (IncludeDetailedUserInfo && !Scope.Contains(Scopes.EmailRead)) { - // EmailRead scope is required to access detailed user info + // EmailRead scope is required to access detailed user info. As the post configure action should have added it, we need to ensure it's present. throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), $"The '{Scopes.EmailRead}' scope must be specified when '{nameof(IncludeDetailedUserInfo)}' is enabled."); } diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs index 40235dd15..b39556353 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyAuthenticationOptionsTests.cs @@ -96,13 +96,28 @@ public static void Validate_Throws_If_UserInformationEndpoint_Is_Null() { ClientId = "my-client-id", ClientSecret = "my-client-secret", - TokenEndpoint = null!, + UserInformationEndpoint = null!, }; // Act and Assert _ = Assert.Throws(nameof(options.UserInformationEndpoint), options.Validate); } + [Fact] + public static void Validate_Dont_Throws_If_DetailedUserInformationEndpoint_Is_Null() + { + // Arrange + var options = new EtsyAuthenticationOptions() + { + ClientId = "my-client-id", + ClientSecret = "my-client-secret", + DetailedUserInfoEndpoint = null!, + }; + + // Act (no Assert) + options.Validate(); + } + [Fact] public static void Validate_Throws_If_CallbackPath_Is_Null() { diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json index bcbf4ab8c..c304d6551 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/bundle.json @@ -2,8 +2,8 @@ "$schema": "https://raw.githubusercontent.com/justeat/httpclient-interception/master/src/HttpClientInterception/Bundles/http-request-bundle-schema.json", "items": [ { - "comment": "Etsy OAuth 2.0 token exchange endpoint response - returns sample token values from Etsy API Docs", - "uri": "https://api.etsy.com/v3/public/oauth/token", + "comment": "Etsy OAuth 2.0 token exchange endpoint response - returns sample token values from Etsy API Docs (domain aligned to openapi.etsy.com to match provider defaults)", + "uri": "https://openapi.etsy.com/v3/public/oauth/token", "method": "POST", "contentFormat": "json", "contentJson": { @@ -24,7 +24,7 @@ }, { "comment": "Etsy /v3/application/users/{user_id} endpoint - returns detailed user information", - "uri": "https://openapi.etsy.com/v3/application/users/{0}", + "uri": "https://openapi.etsy.com/v3/application/users/123456", "contentFormat": "json", "contentJson": { "user_id": 123456, From 05aa969e867af25c1167a848b6485b7114bad591 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Sat, 8 Nov 2025 01:00:26 +0100 Subject: [PATCH 27/32] chore: set InlineData to magic string "urn:etsy:shop_id" because only const strings could be used in attributes, not static readonly strings --- test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs index 5855db247..a23b7c63e 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs @@ -25,7 +25,7 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu [Theory] [InlineData(ClaimTypes.NameIdentifier, "123456")] - [InlineData("shop_id", "789012")] + [InlineData("urn:etsy:shop_id", "789012")] public async Task Can_Sign_In_Using_Etsy(string claimType, string claimValue) => await AuthenticateUserAndAssertClaimValue(claimType, claimValue); From b71fb6f2aa300dcd9e79f209dc444845cc491165 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Sat, 8 Nov 2025 01:05:13 +0100 Subject: [PATCH 28/32] chore(EtsyTests): apply workaround into PostConfigure test User side Post configure is later than provider side post configure. this causes the Scope not to be evaluated and the Mappings are not applyed automatically HACK: add them all manually for the test postConfigure until decision is made this could be put in validate override or always consumer side? --- .../Etsy/EtsyTests.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs index a23b7c63e..e19c71abb 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Etsy/EtsyTests.cs @@ -60,7 +60,17 @@ void ConfigureServices(IServiceCollection services) => services.PostConfigureAll { o.IncludeDetailedUserInfo = true; - // User to include image claim + // Ensure the required scope is present before Validate() executes. + // BUG: This should not be necessary as the post-configure should add it. Assuming test Arrange should simulate eventual user setup. + if (!o.Scope.Contains(Scopes.EmailRead)) + { + o.Scope.Add(Scopes.EmailRead); + o.ClaimActions.MapJsonKey(ClaimTypes.Email, "primary_email"); + o.ClaimActions.MapJsonKey(ClaimTypes.GivenName, "first_name"); + o.ClaimActions.MapJsonKey(ClaimTypes.Surname, "last_name"); + } + + // Opt-in to include image claim (not auto-mapped by provider to reduce payload size) o.ClaimActions.MapImageClaim(); }); From 02991842b0a16e02bda966b0faf4a544e9d17028 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Sat, 8 Nov 2025 01:18:00 +0100 Subject: [PATCH 29/32] docs(PostConfigure): Add Warning that he needs to add the claims himself for detailed user info if he wants to use `PostConfigure` himself, which runs after the Provider side --- docs/etsy.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/etsy.md b/docs/etsy.md index 6394d5980..ce98b0483 100644 --- a/docs/etsy.md +++ b/docs/etsy.md @@ -166,6 +166,9 @@ _Requires `EtsyAuthenticationOptions.IncludeDetailedUserInfo = true`_ | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | `last_name` | ✓ | | `urn:etsy:image_url` | `image_url_75x75` | [Manual](#manually-added-claims) | +> [!WARNING] +> As those claims are set in Provider side `PostConfigureOptions`, you have to include them yourself if you bind from `PostConfigure` also. + #### Manually Added Claims The `image_url_75x75` claim is not auto-mapped to reduce data bloat. You can add it manually via either: From fa13f8bff2ad3fdddef8e092d3ca85d9aeb71ca0 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Sat, 8 Nov 2025 01:25:58 +0100 Subject: [PATCH 30/32] chore: create seperate named log methods TODO: Should be checked, we still need the full methods when using the attribute LoggerMessage. Maybe we can delete the non attribute using method then? --- .../EtsyAuthenticationHandler.cs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs index 4a4fd51ba..e14721531 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationHandler.cs @@ -47,7 +47,7 @@ protected override async Task CreateTicketAsync( using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); if (!response.IsSuccessStatusCode) { - await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted); + await Log.BasicUserInfoErrorAsync(Logger, response, Context.RequestAborted); throw new HttpRequestException("An error occurred while retrieving basic user information from Etsy."); } @@ -117,7 +117,7 @@ protected virtual async Task GetDetailedUserInfoAsync([NotNull] OA using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); if (!response.IsSuccessStatusCode) { - await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted); + await Log.DetailedUserInfoErrorAsync(Logger, response, Context.RequestAborted); throw new HttpRequestException("An error occurred while retrieving detailed user info from Etsy."); } @@ -126,18 +126,38 @@ protected virtual async Task GetDetailedUserInfoAsync([NotNull] OA private static partial class Log { - internal static async Task UserProfileErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken) + internal static async Task BasicUserInfoErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken) { - UserProfileError( + BasicUserInfoError( logger, + response.RequestMessage?.RequestUri?.ToString() ?? string.Empty, response.StatusCode, response.Headers.ToString(), await response.Content.ReadAsStringAsync(cancellationToken)); } - [LoggerMessage(1, LogLevel.Error, "An error occurred while retrieving the user profile from Etsy: the remote server returned a {Status} response with the following payload: {Headers} {Body}.")] - private static partial void UserProfileError( + internal static async Task DetailedUserInfoErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken) + { + DetailedUserInfoError( + logger, + response.RequestMessage?.RequestUri?.ToString() ?? string.Empty, + response.StatusCode, + response.Headers.ToString(), + await response.Content.ReadAsStringAsync(cancellationToken)); + } + + [LoggerMessage(1, LogLevel.Error, "Etsy basic user info request failed for '{RequestUri}': remote server returned a {Status} response with: {Headers} {Body}.")] + private static partial void BasicUserInfoError( + ILogger logger, + string requestUri, + System.Net.HttpStatusCode status, + string headers, + string body); + + [LoggerMessage(2, LogLevel.Error, "Etsy detailed user info request failed for '{RequestUri}': remote server returned a {Status} response with: {Headers} {Body}.")] + private static partial void DetailedUserInfoError( ILogger logger, + string requestUri, System.Net.HttpStatusCode status, string headers, string body); From 36cacb2aca29bee1e0a8f3a526a8d17a1a365d0f Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Sun, 9 Nov 2025 19:03:23 +0100 Subject: [PATCH 31/32] chore: applying PR rewording suggestion Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../EtsyAuthenticationOptions.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs index a1adb5dac..2cd528ae5 100644 --- a/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Etsy/EtsyAuthenticationOptions.cs @@ -44,7 +44,7 @@ public EtsyAuthenticationOptions() /// Gets or sets the endpoint used to retrieve detailed user information. /// /// - /// The placeholder for client_id needs to be "{0}" and will be replaced with the authenticated user's ID. + /// The placeholder for user_id needs to be "{0}" and will be replaced with the authenticated user's ID. /// public string? DetailedUserInfoEndpoint { get; set; } @@ -55,9 +55,10 @@ public override void Validate() { // HACK We want all of the base validation except for ClientSecret, // so rather than re-implement it all, catch the exception thrown - // for that being null and only throw if we aren't using public access type. + // for that being null and only throw if we aren't using public client access type + PKCE. + // Etsy's OAuth implementation does not require a client secret referring to the Documentation using PKCE (Proof Key for Code Exchange). // This does mean that three checks have to be re-implemented - // because the won't be validated if the ClientSecret validation fails. + // because they won't be validated if the ClientSecret validation fails. base.Validate(); } catch (ArgumentException ex) when (ex.ParamName == nameof(ClientSecret)) @@ -89,7 +90,8 @@ public override void Validate() if (IncludeDetailedUserInfo && !Scope.Contains(Scopes.EmailRead)) { - // EmailRead scope is required to access detailed user info. As the post configure action should have added it, we need to ensure it's present. + // EmailRead scope is required to access detailed user info when IncludeDetailedUserInfo is enabled. + // The post configure action should have added it at this stage, so we need to ensure it's present. throw new ArgumentOutOfRangeException(nameof(Scope), string.Join(',', Scope), $"The '{Scopes.EmailRead}' scope must be specified when '{nameof(IncludeDetailedUserInfo)}' is enabled."); } From 3f39b3bf359b4808ffcf847ad7e121155c4a8ce9 Mon Sep 17 00:00:00 2001 From: DevTKSS Date: Sun, 9 Nov 2025 20:26:51 +0100 Subject: [PATCH 32/32] chore: apply PR reword suggestions --- docs/etsy.md | 40 ++++++++++++++----- .../ClaimActionCollectionExtensions.cs | 6 +++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/docs/etsy.md b/docs/etsy.md index ce98b0483..69b8a7201 100644 --- a/docs/etsy.md +++ b/docs/etsy.md @@ -206,6 +206,10 @@ Here is a comprehensive `appsettings.json` example covering supported options an "Etsy": { "ClientId": "your-etsy-api-key", "IncludeDetailedUserInfo": true, + "DetailedUserInfoEndpoint": "https://openapi.etsy.com/v3/application/users/{0}", + "AuthorizationEndpoint": "https://www.etsy.com/oauth/connect", + "TokenEndpoint": "https://openapi.etsy.com/v3/public/oauth/token", + "UserInformationEndpoint": "https://openapi.etsy.com/v3/application/users/me", "SaveTokens": true, "Scopes": [ "shops_r", "email_r" ] } @@ -215,7 +219,9 @@ Here is a comprehensive `appsettings.json` example covering supported options an > [!NOTE] > We recommend saving tokens (`SaveTokens = true`) to facilitate token refresh, so the user does not need to re-authenticate frequently. > [!NOTE] -> If you bind from appsettings, make sure to either set no scopes, if the basic `shops_r` scope is sufficient (`IncludeDetailedUserInfo` set to `true` would also add `email_r`), or always include them into the configuration. The handler does not automatically add it when binding from configuration. +> If `IncludeDetailedUserInfo` is set to `true` and the scopes `shops_r` and `email_r` scopes are sufficient, you don't need to set additional scopes in `appsettings.json`, they are added automatically. +> [!TIP] +> We recommend using the `EtsyAuthenticationDefaults` class in your `.AddEtsy` call which contains the default endpoint URLs. If you bind then from configuration, set the options in code, for example: @@ -228,21 +234,35 @@ builder.Services.AddAuthentication(options => .AddCookie() .AddEtsy(options => { - var section = builder.Configuration.GetSection("Etsy").Get(); - options.ClientId = section.ClientId!; - options.IncludeDetailedUserInfo = section.IncludeDetailedUserInfo; - options.SaveTokens = section.SaveTokens; + var section = builder.Configuration.GetSection("Etsy"); + options.ClientId = section.GetValue("ClientId") + ?? throw new InvalidOperationException("Etsy:ClientId configuration value is missing."); + + // For the Detailed User Info Endpoint and SaveTokens are default values pre-set, but you can override them here + options.IncludeDetailedUserInfo = section.GetValue("IncludeDetailedUserInfo"); + options.SaveTokens = section.GetValue("SaveTokens"); + + // Use the defaults from EtsyAuthenticationDefaults, if you want to repeat them here, but they are set automatically + options.AuthorizationEndpoint = EtsyAuthenticationDefaults.AuthorizationEndpoint; + options.TokenEndpoint = EtsyAuthenticationDefaults.TokenEndpoint; + options.UserInformationEndpoint = EtsyAuthenticationDefaults.UserInformationEndpoint; // Apply scopes from config if present - var scopes = section.GetSection("Scopes").Get(); + var scopes = section.Get("Scopes"); - foreach (var scope in scopes) + if (scopes is not null) { - options.Scope.Add(scope); + foreach (var scope in scopes) + { + options.Scope.Add(scope); + } } - // Optionally add image claim - options.AddImageClaim(); + // Optionally Map the image claim + options.ClaimActions.MapImageClaim(); + + // Map other Claims + options.ClaimActions.MapJsonKey("urn:etsy:listingsWrite", "listings_w"); }) ``` diff --git a/src/AspNet.Security.OAuth.Etsy/ClaimActionCollectionExtensions.cs b/src/AspNet.Security.OAuth.Etsy/ClaimActionCollectionExtensions.cs index a9e5c8cc2..d8f26be1b 100644 --- a/src/AspNet.Security.OAuth.Etsy/ClaimActionCollectionExtensions.cs +++ b/src/AspNet.Security.OAuth.Etsy/ClaimActionCollectionExtensions.cs @@ -9,8 +9,14 @@ namespace Microsoft.Extensions.DependencyInjection; +/// +/// Provides extension methods for to map Etsy API specific user claims. +/// public static class ClaimActionCollectionExtensions { + /// + /// Maps the Etsy user's profile image URL (75x75) to the claim. + /// public static void MapImageClaim(this ClaimActionCollection collection) { collection.MapJsonKey(EtsyAuthenticationConstants.Claims.ImageUrl, "image_url_75x75");