public static void BuildInfoProvider_EmptyBuildInfo()
        {
            using (var directory = new TemporaryDirectory())
            {
                var mockWebHostEnvironment = new Mock <IWebHostEnvironment>(MockBehavior.Strict);
                mockWebHostEnvironment.Setup(_ => _.WebRootPath).Returns(directory.Location).Verifiable();

                Directory.CreateDirectory(Path.Combine(directory.Location, "data"));
                File.WriteAllText(Path.Combine(directory.Location, @"data\BuildInfo.json"), "{}");
                File.WriteAllText(Path.Combine(directory.Location, @"manifest.json"), JsonConvert.SerializeObject(Manifest));

                var provider = new BuildInfoProvider(mockWebHostEnvironment.Object);

                Assert.Null(provider.BuildId);
                Assert.Null(provider.Changelist);

                Assert.NotNull(provider.Webclient);
                Assert.Equal(4, provider.Webclient.Count);
                Assert.Equal("39caf45e53fcc3060725", provider.Webclient["runtime"]);
                Assert.Equal("7b01886e415366cd2f5c", provider.Webclient["vendors~app"]);
                Assert.Equal("20c89b387dbdb102dba5", provider.Webclient["data~app"]);
                Assert.Equal("d8df654c29474da7b106", provider.Webclient["app"]);

                mockWebHostEnvironment.Verify();
            }
        }
Exemple #2
0
        /// <summary>
        ///     Writes out <c>BuildInfo</c> prior to build.
        /// </summary>
        public override void OnBeforeBuild()
        {
            GDXConfig config = GDXConfig.Get();

            if (config == null || !config.developerBuildInfoEnabled)
            {
                return;
            }

            // Cache for destructor
            _enabled = true;

            try
            {
                string path = Path.Combine(Application.dataPath, config.developerBuildInfoPath);
                Platform.EnsureFileFolderHierarchyExists(path);
                File.WriteAllText(path, BuildInfoProvider.GetContent(config, false, Context.BuildConfigurationName));

                BuildInfoProvider.CheckForAssemblyDefinition();
            }
            catch (Exception e)
            {
                Debug.LogWarning(e);
            }
        }
Exemple #3
0
        /// <summary>
        ///     Restores the default <c>BuildInfo</c> after a build process finishes.
        /// </summary>
        /// <param name="report">Build process reported information.</param>
        public void OnPostprocessBuild(BuildReport report)
        {
            GDXConfig config = GDXConfig.Get();

            if (config == null || !config.developerBuildInfoEnabled)
            {
                return;
            }

            BuildInfoProvider.WriteDefaultFile();
        }
        public static void BuildInfoProvider_EmptyManifest()
        {
            using (var directory = new TemporaryDirectory())
            {
                var mockWebHostEnvironment = new Mock <IWebHostEnvironment>(MockBehavior.Strict);
                mockWebHostEnvironment.Setup(_ => _.WebRootPath).Returns(directory.Location).Verifiable();

                Directory.CreateDirectory(Path.Combine(directory.Location, "data"));
                File.WriteAllText(Path.Combine(directory.Location, @"data\BuildInfo.json"), JsonConvert.SerializeObject(BuildInfo));
                File.WriteAllText(Path.Combine(directory.Location, @"manifest.json"), "{}");

                var provider = new BuildInfoProvider(mockWebHostEnvironment.Object);

                Assert.Equal("SomeBuildId", provider.BuildId);
                Assert.Equal("SomeChangelist", provider.Changelist);

                Assert.NotNull(provider.Webclient);
                Assert.Equal(0, provider.Webclient.Count);

                mockWebHostEnvironment.Verify();
            }
        }
Exemple #5
0
        /// <summary>
        ///     Writes out <c>BuildInfo</c> prior to build.
        /// </summary>
        /// <param name="report">Build process reported information.</param>
        public void OnPreprocessBuild(BuildReport report)
        {
            GDXConfig config = GDXConfig.Get();

            if (config == null || !config.developerBuildInfoEnabled)
            {
                return;
            }

            try
            {
                string path = Path.Combine(Application.dataPath, config.developerBuildInfoPath);
                Platform.EnsureFileFolderHierarchyExists(path);
                File.WriteAllText(path, BuildInfoProvider.GetContent(config, false, "Legacy"));

                BuildInfoProvider.CheckForAssemblyDefinition();
            }
            catch (Exception e)
            {
                Debug.LogWarning(e);
            }
        }
Exemple #6
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // The DevelopmentStorageAccount will only work if you have the Storage emulator v4.3 installed: https://go.microsoft.com/fwlink/?linkid=717179&clcid=0x409
            var storageConnectionString        = this.configuration["Storage:ConnectionString"];
            CloudStorageAccount storageAccount = null;

            if (!string.IsNullOrEmpty(storageConnectionString))
            {
                storageAccount = CloudStorageAccount.Parse(storageConnectionString);
            }

            // Necessary to persist keys (like the ones used to generate auth cookies)
            // By default Azure Websites can persist keys across instances within a slot, but not across slots.
            // This means a slot swap will require users to re-log in.
            // See https://github.com/aspnet/Home/issues/466 and https://github.com/aspnet/DataProtection/issues/92 for details.
            var dataProtectionBuilder = services.AddDataProtection();

            if (!string.IsNullOrEmpty(storageConnectionString))
            {
                dataProtectionBuilder.PersistKeysToAzureBlobStorage(storageConnectionString, "key-container", "keys.xml");
            }

            // Add Entity framework services.
            services.AddDbContext <ApplicationDbContext>(options =>
            {
                options.UseSqlServer(this.configuration["Database:ConnectionString"]);

                // Register the entity sets needed by OpenIddict.
                options.UseOpenIddict();
            });

            services.AddIdentity <ApplicationUser, IdentityRole>(options =>
            {
                // We need to disallow '@' since we need to disambiguate between user names and email addresses during log in
                options.User.AllowedUserNameCharacters = options.User.AllowedUserNameCharacters.Replace("@", string.Empty, StringComparison.Ordinal);

                options.User.RequireUniqueEmail = true;

                options.Password.RequiredLength         = 4;
                options.Password.RequireDigit           = false;
                options.Password.RequireLowercase       = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase       = false;

                // Configure Identity to use the same JWT claims as OpenIddict instead
                // of the legacy WS-Federation claims it uses by default (ClaimTypes),
                // which saves you from doing the mapping in your authorization controller.
                options.ClaimsIdentity.UserNameClaimType = OpenIddictConstants.Claims.Name;
                options.ClaimsIdentity.UserIdClaimType   = OpenIddictConstants.Claims.Subject;
                options.ClaimsIdentity.EmailClaimType    = OpenIddictConstants.Claims.Email;
                options.ClaimsIdentity.RoleClaimType     = OpenIddictConstants.Claims.Role;
            })
            .AddEntityFrameworkStores <ApplicationDbContext>()
            .AddDefaultTokenProviders();

            // Register the OpenIddict services.
            services.AddOpenIddict()

            // Register the OpenIddict core services.
            .AddCore(options =>
            {
                // Configure OpenIddict to use the Entity Framework Core stores and models.
                options.UseEntityFrameworkCore()
                .UseDbContext <ApplicationDbContext>();
            })

            // Register the OpenIddict server handler.
            .AddServer(options =>
            {
                // Enable the token endpoint (required to use the password flow).
                options.SetTokenEndpointUris("/api/auth/token");

                // Allow client applications to use the grant_type=password flow.
                options.AllowPasswordFlow()
                .AllowRefreshTokenFlow()
                .AllowCustomFlow(GoogleAssertionGrantHandler.GrantType)
                .AllowCustomFlow(FacebookAssertionGrantHandler.GrantType)
                .AllowCustomFlow(MicrosoftAssertionGrantHandler.GrantType);

                // Mark the "email", "profile" and "roles" scopes as supported scopes.
                options.RegisterScopes(
                    OpenIddictConstants.Scopes.Email,
                    OpenIddictConstants.Scopes.Profile,
                    OpenIddictConstants.Scopes.Roles);

                // Accept anonymous clients (i.e clients that don't send a client_id).
                options.AcceptAnonymousClients();

                // Use ASP.NET Core data protection
                options.UseDataProtection();

                // Register the signing and encryption credentials.
                // Use ephemeral keys since we're using Data Protection to validate and issue tokens anyway,
                // but OpenIddict doesn't allow us to not register anything.
                options.AddEphemeralEncryptionKey()
                .AddEphemeralSigningKey();

                // When rolling tokens are enabled, immediately
                // redeem the refresh token to prevent future reuse.
                options.UseRollingRefreshTokens();

                // Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
                options.UseAspNetCore()
                .EnableTokenEndpointPassthrough();
            })

            // Register the OpenIddict validation components.
            .AddValidation(options =>
            {
                // Import the configuration from the local OpenIddict server instance.
                options.UseLocalServer();

                // Register the ASP.NET Core host.
                options.UseAspNetCore();

                // Use ASP.NET Core data protection
                options.UseDataProtection();
            });

            services.Configure((AssertionGrantOptions options) =>
            {
                options.AddAssertionGrantType <GoogleAssertionGrantHandler>(GoogleAssertionGrantHandler.GrantType);
                options.AddAssertionGrantType <FacebookAssertionGrantHandler>(FacebookAssertionGrantHandler.GrantType);
                options.AddAssertionGrantType <MicrosoftAssertionGrantHandler>(MicrosoftAssertionGrantHandler.GrantType);
            });

            services.AddSingleton <GoogleAssertionGrantHandler>();
            services.AddSingleton <FacebookAssertionGrantHandler>();
            services.AddSingleton <MicrosoftAssertionGrantHandler>();

            services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder()
                                        .AddAuthenticationSchemes(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)
                                        .RequireAuthenticatedUser()
                                        .Build();
            });

            var buildInfoProvider = new BuildInfoProvider(this.environment);

            services.AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions
            {
                ApplicationVersion = buildInfoProvider.BuildId,
                DeveloperMode      = this.environment.IsDevelopment(),
            });

            services.AddCors();

            services.AddControllers()
            .AddJsonOptions(options =>
            {
                // Beautify by default for debuggability. When gzipping, this barely adds anything to the payload.
                options.JsonSerializerOptions.WriteIndented = true;

                // Omit nulls
                options.JsonSerializerOptions.IgnoreNullValues = true;

                // Use camel-casing for fields (lower case first character)
                options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

                // Convert enum values to strings
                options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
            });

            // Allow IOptions<T> to be available through DI
            services.AddOptions();

            // Container controlled registrations
            if (storageAccount != null)
            {
                services.AddSingleton <CloudTableClient>(_ => storageAccount.CreateCloudTableClient());
                services.AddSingleton <ISiteNewsProvider, AzureStorageSiteNewsProvider>();
            }
            else
            {
                services.AddSingleton <ISiteNewsProvider, InMemorySiteNewsProvider>();
            }

            services.AddSingleton <GameData>(_ => GameData.Parse(Path.Combine(this.environment.WebRootPath, @"data\GameData.json")));
            services.AddSingleton <HttpClient>(_ => new HttpClient());
            services.AddSingleton <IAssertionGrantHandlerProvider, AssertionGrantHandlerProvider>();
            services.AddSingleton <IBuildInfoProvider>(buildInfoProvider);
            services.AddSingleton <IEmailSender, EmailSender>();
            services.AddSingleton <IOptions <PasswordHasherOptions>, PasswordHasherOptionsAccessor>();

            // Per request registrations
            services.AddScoped <IClanManager, ClanManager>();
            services.AddScoped <IDatabaseCommandFactory, DatabaseCommandFactory>();
            services.AddScoped <IUserSettingsProvider, UserSettingsProvider>();

            // configuration
            services.Configure <AuthenticationSettings>(options => this.configuration.GetSection("Authentication").Bind(options));
            services.Configure <DatabaseSettings>(options => this.configuration.GetSection("Database").Bind(options));
            services.Configure <EmailSenderSettings>(options => this.configuration.GetSection("EmailSender").Bind(options));
        }
Exemple #7
0
            /// <summary>
            ///     Draw the Build Info section of settings.
            /// </summary>
            /// <param name="settings">Serialized <see cref="GDXConfig" /> object to be modified.</param>
            public static void BuildInfo(SerializedObject settings)
            {
                const string sectionID = "GDX.Editor.Build.BuildInfoProvider";

                GUI.enabled = true;

                bool buildInfoEnabled = Layout.CreateSettingsSection(
                    sectionID, false,
                    "BuildInfo Generation", $"{DocumentationUri}api/GDX.Editor.Build.BuildInfoProvider.html",
                    settings.FindProperty("developerBuildInfoEnabled"),
                    Content.BuildInfoEnabled);

                if (!Layout.GetCachedEditorBoolean(sectionID))
                {
                    return;
                }

                string buildInfoFile = Path.Combine(Application.dataPath,
                                                    settings.FindProperty("developerBuildInfoPath").stringValue);

                if (!File.Exists(buildInfoFile))
                {
                    GUILayout.BeginVertical(Styles.InfoBoxStyle);
                    GUILayout.BeginHorizontal();
                    GUILayout.Label(
                        "There is currently no BuildInfo file in the target location. Would you like some default content written in its place?",
                        Styles.WordWrappedLabelStyle);
                    if (GUILayout.Button("Create Default", Styles.ButtonStyle))
                    {
                        BuildInfoProvider.WriteDefaultFile();
                        AssetDatabase.ImportAsset("Assets/" +
                                                  settings.FindProperty("developerBuildInfoPath").stringValue);
                    }

                    GUILayout.EndHorizontal();
                    GUILayout.EndVertical();
                }

                // Only allow editing based on the feature being enabled
                GUI.enabled = buildInfoEnabled;

                EditorGUILayout.PropertyField(settings.FindProperty("developerBuildInfoPath"),
                                              Content.BuildInfoPath);
                EditorGUILayout.PropertyField(settings.FindProperty("developerBuildInfoNamespace"),
                                              Content.BuildInfoNamespace);
                EditorGUILayout.PropertyField(settings.FindProperty("developerBuildInfoAssemblyDefinition"),
                                              Content.BuildInfoAssemblyDefinition);


                GUILayout.Space(10);
                // Arguments (we're going to make sure they are forced to uppercase).
                GUILayout.Label("Build Arguments", Styles.SubSectionHeaderTextStyle);

                SerializedProperty buildNumberProperty =
                    settings.FindProperty("developerBuildInfoBuildNumberArgument");

                EditorGUILayout.PropertyField(buildNumberProperty, Content.BuildInfoBuildNumberArgument);
                if (buildNumberProperty.stringValue.HasLowerCase())
                {
                    buildNumberProperty.stringValue = buildNumberProperty.stringValue.ToUpper();
                }

                SerializedProperty buildDescriptionProperty =
                    settings.FindProperty("developerBuildInfoBuildDescriptionArgument");

                EditorGUILayout.PropertyField(buildDescriptionProperty,
                                              Content.BuildInfoBuildDescriptionArgument);
                if (buildDescriptionProperty.stringValue.HasLowerCase())
                {
                    buildDescriptionProperty.stringValue = buildDescriptionProperty.stringValue.ToUpper();
                }

                SerializedProperty buildChangelistProperty =
                    settings.FindProperty("developerBuildInfoBuildChangelistArgument");

                EditorGUILayout.PropertyField(buildChangelistProperty,
                                              Content.BuildInfoBuildChangelistArgument);
                if (buildChangelistProperty.stringValue.HasLowerCase())
                {
                    buildChangelistProperty.stringValue = buildChangelistProperty.stringValue.ToUpper();
                }

                SerializedProperty buildTaskProperty = settings.FindProperty("developerBuildInfoBuildTaskArgument");

                EditorGUILayout.PropertyField(buildTaskProperty, Content.BuildInfoBuildTaskArgument);
                if (buildTaskProperty.stringValue.HasLowerCase())
                {
                    buildTaskProperty.stringValue = buildTaskProperty.stringValue.ToUpper();
                }

                SerializedProperty buildStreamProperty =
                    settings.FindProperty("developerBuildInfoBuildStreamArgument");

                EditorGUILayout.PropertyField(buildStreamProperty, Content.BuildInfoBuildStreamArgument);
                if (buildStreamProperty.stringValue.HasLowerCase())
                {
                    buildStreamProperty.stringValue = buildStreamProperty.stringValue.ToUpper();
                }
            }
Exemple #8
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // The DevelopmentStorageAccount will only work if you have the Storage emulator v4.3 installed: https://go.microsoft.com/fwlink/?linkid=717179&clcid=0x409
            var storageConnectionString = this.configuration["Storage:ConnectionString"];
            var storageAccount          = !string.IsNullOrEmpty(storageConnectionString)
                ? CloudStorageAccount.Parse(storageConnectionString)
                : null;

            // Nesessary to persist keys (like the ones used to generate auth cookies)
            // By default Azure Websites can persist keys across instances within a slot, but not across slots.
            // This means a slot swap will require users to re-log in.
            // See https://github.com/aspnet/Home/issues/466 and https://github.com/aspnet/DataProtection/issues/92 for details.
            var dataProtectionBuilder = services.AddDataProtection();

            if (storageAccount != null)
            {
                var client    = storageAccount.CreateCloudBlobClient();
                var container = client.GetContainerReference("key-container");

                // The container must exist before calling the DataProtection APIs.
                // The specific file within the container does not have to exist,
                // as it will be created on-demand.
                container.CreateIfNotExistsAsync().Wait();

                dataProtectionBuilder.PersistKeysToAzureBlobStorage(container, "keys.xml");
            }

            // Add Entity framework services.
            services.AddDbContext <ApplicationDbContext>(options =>
            {
                options.UseSqlServer(this.configuration["Database:ConnectionString"]);

                // Register the entity sets needed by OpenIddict.
                options.UseOpenIddict();
            });

            services.AddIdentity <ApplicationUser, IdentityRole>(options =>
            {
                // We need to disallow '@' since we need to disambiguate between user names and email addresses during log in
                options.User.AllowedUserNameCharacters = options.User.AllowedUserNameCharacters.Replace("@", string.Empty, StringComparison.Ordinal);

                options.User.RequireUniqueEmail = true;

                options.Password.RequiredLength         = 4;
                options.Password.RequireDigit           = false;
                options.Password.RequireLowercase       = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase       = false;

                // Configure Identity to use the same JWT claims as OpenIddict instead
                // of the legacy WS-Federation claims it uses by default (ClaimTypes),
                // which saves you from doing the mapping in your authorization controller.
                options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
                options.ClaimsIdentity.UserIdClaimType   = OpenIdConnectConstants.Claims.Subject;
                options.ClaimsIdentity.RoleClaimType     = OpenIdConnectConstants.Claims.Role;
            })
            .AddEntityFrameworkStores <ApplicationDbContext>()
            .AddDefaultTokenProviders();

            // Register the OpenIddict services.
            services.AddOpenIddict()

            // Register the OpenIddict core services.
            .AddCore(options =>
            {
                // Configure OpenIddict to use the Entity Framework Core stores and models.
                options.UseEntityFrameworkCore()
                .UseDbContext <ApplicationDbContext>();
            })

            // Register the OpenIddict server handler.
            .AddServer(options =>
            {
                // Register the ASP.NET Core MVC services used by OpenIddict.
                // Note: if you don't call this method, you won't be able to
                // bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
                options.UseMvc();

                // Enable the token endpoint (required to use the password flow).
                options.EnableTokenEndpoint("/api/auth/token");

                // Allow client applications to use the grant_type=password flow.
                options.AllowPasswordFlow()
                .AllowRefreshTokenFlow()
                .AllowCustomFlow(GoogleAssertionGrantHandler.GrantType)
                .AllowCustomFlow(FacebookAssertionGrantHandler.GrantType)
                .AllowCustomFlow(MicrosoftAssertionGrantHandler.GrantType);

                // Mark the "email", "profile" and "roles" scopes as supported scopes.
                options.RegisterScopes(
                    OpenIdConnectConstants.Scopes.Email,
                    OpenIdConnectConstants.Scopes.Profile,
                    OpenIddictConstants.Scopes.Roles);

                // When request caching is enabled, authorization and logout requests
                // are stored in the distributed cache by OpenIddict and the user agent
                // is redirected to the same page with a single parameter (request_id).
                // This allows flowing large OpenID Connect requests even when using
                // an external authentication provider like Google, Facebook or Twitter.
                options.EnableRequestCaching();

                // We don't want to specify a client_id when sending a token or revocation request.
                options.AcceptAnonymousClients();

                // When rolling tokens are enabled, immediately
                // redeem the refresh token to prevent future reuse.
                options.UseRollingTokens();
            })

            // Register the OpenIddict validation handler.
            // Note: the OpenIddict validation handler is only compatible with the
            // default token format or with reference tokens and cannot be used with
            // JWT tokens. For JWT tokens, use the Microsoft JWT bearer handler.
            .AddValidation();

            services.Configure((AssertionGrantOptions options) =>
            {
                options.AddAssertionGrantType <GoogleAssertionGrantHandler>(GoogleAssertionGrantHandler.GrantType);
                options.AddAssertionGrantType <FacebookAssertionGrantHandler>(FacebookAssertionGrantHandler.GrantType);
                options.AddAssertionGrantType <MicrosoftAssertionGrantHandler>(MicrosoftAssertionGrantHandler.GrantType);
            });

            services.AddSingleton <GoogleAssertionGrantHandler>();
            services.AddSingleton <FacebookAssertionGrantHandler>();
            services.AddSingleton <MicrosoftAssertionGrantHandler>();

            services.AddAuthentication();

            services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder()
                                        .AddAuthenticationSchemes(OAuthValidationDefaults.AuthenticationScheme)
                                        .RequireAuthenticatedUser()
                                        .Build();
            });

            var buildInfoProvider = new BuildInfoProvider(this.environment);

            services.Configure((ApplicationInsightsServiceOptions options) =>
            {
                options.ApplicationVersion = buildInfoProvider.BuildId;
                options.DeveloperMode      = this.environment.IsDevelopment();
            });

            services.AddCors();

            services.AddMvc(options =>
            {
                ////var jsonFormatter = options.OutputFormatters.OfType<NewtonsoftJsonOutputFormatter>().Single();

                // Allow the json formatter to handle requests from the browser
                ////jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
            }).AddNewtonsoftJson(options =>
            {
                // Beautify by default for debuggability. When gzipping, this barely adds anything to the payload.
                options.SerializerSettings.Formatting = Formatting.Indented;

                // Omit nulls
                options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;

                // Use camel-casing for fields (lower case first character)
                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

                // Convert enum values to strings
                options.SerializerSettings.Converters.Add(new StringEnumConverter {
                    NamingStrategy = new CamelCaseNamingStrategy()
                });
            });

            // Allow IOptions<T> to be available through DI
            services.AddOptions();

            // Container controlled registrations
            if (storageAccount != null)
            {
                services.AddSingleton <CloudTableClient>(_ => storageAccount.CreateCloudTableClient());
                services.AddSingleton <CloudQueueClient>(_ => storageAccount.CreateCloudQueueClient());
                services.AddSingleton <IUploadScheduler, AzureStorageUploadScheduler>();
                services.AddSingleton <ISiteNewsProvider, AzureStorageSiteNewsProvider>();
            }
            else
            {
                services.AddSingleton <IUploadScheduler, NoOpUploadScheduler>();
                services.AddSingleton <ISiteNewsProvider, InMemorySiteNewsProvider>();
            }

            services.AddSingleton <GameData>(_ => GameData.Parse(Path.Combine(this.environment.WebRootPath, @"data\GameData.json")));
            services.AddSingleton <HttpClient>(_ => new HttpClient());
            services.AddSingleton <IAssertionGrantHandlerProvider, AssertionGrantHandlerProvider>();
            services.AddSingleton <IBuildInfoProvider>(buildInfoProvider);
            services.AddSingleton <IEmailSender, EmailSender>();
            services.AddSingleton <IOptions <PasswordHasherOptions>, PasswordHasherOptionsAccessor>();

            // Per request registrations
            services.AddScoped <IClanManager, ClanManager>();
            services.AddScoped <IDatabaseCommandFactory, DatabaseCommandFactory>();
            services.AddScoped <IUserSettingsProvider, UserSettingsProvider>();

            // configuration
            services.Configure <AuthenticationSettings>(options => this.configuration.GetSection("Authentication").Bind(options));
            services.Configure <DatabaseSettings>(options => this.configuration.GetSection("Database").Bind(options));
            services.Configure <EmailSenderSettings>(options => this.configuration.GetSection("EmailSender").Bind(options));
        }
        public void True_GetContent_ForceDefaults()
        {
            string generateContent = BuildInfoProvider.GetContent(GDXConfig.Get(), true);

            Assert.IsTrue(generateContent.Contains(" public const int Changelist = 0;"), "Expected to find 'public const int Changelist = 0;'");
        }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // The DevelopmentStorageAccount will only work if you have the Storage emulator v4.3 installed: https://go.microsoft.com/fwlink/?linkid=717179&clcid=0x409
            var storageConnectionString = this.Configuration["Storage:ConnectionString"];
            var storageAccount          = !string.IsNullOrEmpty(storageConnectionString)
                ? CloudStorageAccount.Parse(storageConnectionString)
                : null;

            // Nesessary to persist keys (like the ones used to generate auth cookies)
            // By default Azure Websites can persist keys across instances within a slot, but not across slots.
            // This means a slot swap will require users to re-log in.
            // See https://github.com/aspnet/Home/issues/466 and https://github.com/aspnet/DataProtection/issues/92 for details.
            var dataProtectionBuilder = services.AddDataProtection();

            if (storageAccount != null)
            {
                var client    = storageAccount.CreateCloudBlobClient();
                var container = client.GetContainerReference("key-container");

                // The container must exist before calling the DataProtection APIs.
                // The specific file within the container does not have to exist,
                // as it will be created on-demand.
                container.CreateIfNotExistsAsync().Wait();

                dataProtectionBuilder.PersistKeysToAzureBlobStorage(container, "keys.xml");
            }

            // Add Entity framework services.
            var connectionString = this.Configuration["Database:ConnectionString"];
            Action <DbContextOptionsBuilder> useDatabase;

            switch (this.Configuration["Database:Kind"])
            {
            case "SqlServer":
            {
                useDatabase = options => options.UseSqlServer(connectionString);
                break;
            }

            case "Sqlite":
            {
                useDatabase = options => options.UseSqlite(connectionString);
                break;
            }

            default:
            {
                throw new InvalidOperationException($"Invalid configuration for \"Database:Kind\": {this.Configuration["Database:Kind"]}");
            }
            }

            services.AddDbContext <ApplicationDbContext>(options =>
            {
                useDatabase(options);

                // Register the entity sets needed by OpenIddict.
                options.UseOpenIddict();
            });

            services.AddIdentity <ApplicationUser, IdentityRole>(options =>
            {
                // We need to disallow '@' since we need to disambiguate between user names and email addresses during log in
                options.User.AllowedUserNameCharacters = options.User.AllowedUserNameCharacters.Replace("@", string.Empty, StringComparison.Ordinal);

                options.User.RequireUniqueEmail = true;

                options.Password.RequiredLength         = 4;
                options.Password.RequireDigit           = false;
                options.Password.RequireLowercase       = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase       = false;

                // Configure Identity to use the same JWT claims as OpenIddict instead
                // of the legacy WS-Federation claims it uses by default (ClaimTypes),
                // which saves you from doing the mapping in your authorization controller.
                options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
                options.ClaimsIdentity.UserIdClaimType   = OpenIdConnectConstants.Claims.Subject;
                options.ClaimsIdentity.RoleClaimType     = OpenIdConnectConstants.Claims.Role;
            })
            .AddEntityFrameworkStores <ApplicationDbContext>()
            .AddDefaultTokenProviders();

            // Register the OpenIddict services.
            services.AddOpenIddict(options =>
            {
                // Register the Entity Framework stores.
                options.AddEntityFrameworkCoreStores <ApplicationDbContext>();

                // Register the ASP.NET Core MVC binder used by OpenIddict.
                // Note: if you don't call this method, you won't be able to
                // bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
                options.AddMvcBinders();

                // Enable the token endpoint (required to use the password flow).
                options.EnableTokenEndpoint("/api/auth/token");

                // Allow client applications to use the grant_type=password flow.
                options.AllowPasswordFlow()
                .AllowRefreshTokenFlow();

                // Allow Http on devbox
                if (this.Environment.IsDevelopment())
                {
                    options.DisableHttpsRequirement();
                }
            });

            this.ConfigureAuthentication(services);

            var buildInfoProvider = new BuildInfoProvider(this.Environment);

            services.AddApplicationInsightsTelemetry(this.Configuration);
            services.Configure((ApplicationInsightsServiceOptions options) =>
            {
                options.ApplicationVersion = buildInfoProvider.BuildId;
                options.EnableAuthenticationTrackingJavaScript = true;
            });

            services.AddCors();

            services.AddMvc(options =>
            {
                options.Filters.Add(typeof(MeasureLatencyFilter));

                var jsonFormatter = options.OutputFormatters.OfType <JsonOutputFormatter>().Single();

                // Allow the json formatter to handle requests from the browser
                jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
            }).AddJsonOptions(options =>
            {
                // Beautify by default for debuggability. When gzipping, this barely adds anything to the payload.
                options.SerializerSettings.Formatting = Formatting.Indented;

                // Omit nulls
                options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;

                // Use camel-casing for fields (lower case first character)
                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

                // Convert enum values to strings
                options.SerializerSettings.Converters.Add(new StringEnumConverter {
                    CamelCaseText = true
                });
            });

            // Allow IOptions<T> to be available through DI
            services.AddOptions();

            // Container controlled registrations
            if (storageAccount != null)
            {
                services.AddSingleton <CloudTableClient>(_ => storageAccount.CreateCloudTableClient());
                services.AddSingleton <CloudQueueClient>(_ => storageAccount.CreateCloudQueueClient());
                services.AddSingleton <IUploadScheduler, AzureStorageUploadScheduler>();
                services.AddSingleton <ISiteNewsProvider, AzureStorageSiteNewsProvider>();
            }
            else
            {
                services.AddSingleton <IUploadScheduler, NoOpUploadScheduler>();
                services.AddSingleton <ISiteNewsProvider, InMemorySiteNewsProvider>();
            }

            services.AddSingleton <GameData>(_ => GameData.Parse(Path.Combine(this.Environment.WebRootPath, @"data\GameData.json")));
            services.AddSingleton <IBuildInfoProvider>(buildInfoProvider);
            services.AddSingleton <IEmailSender, EmailSender>();
            services.AddSingleton <IOptions <PasswordHasherOptions>, PasswordHasherOptionsAccessor>();

            // Per request registrations
            services.AddScoped <IContentManager, ContentManager>();
            services.AddScoped <ICounterProvider, CounterProvider>();
            services.AddScoped <IDatabaseCommandFactory, DatabaseCommandFactory>();
            services.AddScoped <IUserSettingsProvider, UserSettingsProvider>();

            // Configuration
            services.Configure <DatabaseSettings>(options => this.Configuration.GetSection("Database").Bind(options));
            services.Configure <EmailSenderSettings>(options => this.Configuration.GetSection("EmailSender").Bind(options));
        }