using Audit.WebApi; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using RMuseum.DbContext; using RMuseum.Models.Auth.Memory; using RMuseum.Services; using RMuseum.Services.Implementation; using RMuseum.Services.Implementationa; using RSecurityBackend.Authorization; using RSecurityBackend.DbContext; using RSecurityBackend.Models.Auth.Db; using RSecurityBackend.Models.Auth.Memory; using RSecurityBackend.Models.Mail; using RSecurityBackend.Services; using RSecurityBackend.Services.Implementation; using RSecurityBackend.Utilities; using Swashbuckle.AspNetCore.Filters; using System; using System.IO; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace RMuseum { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add service and create Policy with options services.AddCors(options => { options.AddPolicy("GanjoorCorsPolicy", builder => builder.SetIsOriginAllowed(_ => true) .AllowAnyMethod() .AllowAnyHeader() .WithExposedHeaders("paging-headers", "audio-upload-enabled") .AllowCredentials() ); }); services.AddDbContext(); Audit.Core.Configuration.JsonSettings.ContractResolver = AuditNetEnvironmentSkippingContractResolver.Instance; Audit.Core.Configuration.DataProvider = new RAuditDataProvider(Configuration.GetConnectionString("DefaultConnection")); services.AddIdentityCore( options => { // Password settings. options.Password.RequireDigit = true; options.Password.RequireLowercase = true; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequiredLength = 6; options.Password.RequiredUniqueChars = 1; // Lockout settings. options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); options.Lockout.MaxFailedAccessAttempts = 5; options.Lockout.AllowedForNewUsers = true; // User settings. options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; options.User.RequireUniqueEmail = false; } ).AddErrorDescriber(); new IdentityBuilder(typeof(RAppUser), typeof(RAppRole), services) .AddRoleManager>() .AddSignInManager>() .AddEntityFrameworkStores() .AddErrorDescriber(); services.AddMvc(mvc => mvc.AddAuditFilter(config => config .LogRequestIf(r => r.Method != "GET") .WithEventType("{controller}/{action} ({verb})") .IncludeHeaders(ctx => !ctx.ModelState.IsValid) .IncludeRequestBody() .IncludeModelState() )).SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddMemoryCache(); services.Configure(x => { x.ValueLengthLimit = int.MaxValue; x.MultipartBodyLengthLimit = int.MaxValue; // In case of multipart }); services.AddAuthentication(options => { options.DefaultScheme = "bearer"; }).AddJwtBearer("bearer", options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateAudience = false, ValidAudience = "Everyone", ValidateIssuer = true, ValidIssuer = "Ganjoor", ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes($"{Configuration.GetSection("Security")["Secret"]}")), ValidateLifetime = true, //validate the expiration and not before values in the token ClockSkew = TimeSpan.Zero }; options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired", "true"); } return Task.CompletedTask; } }; }); services.AddAuthorization(options => { //this is the default policy to make sure the use session has not yet been deleted by him/her from another client //or by an admin (Authorize with no policy should fail on deleted sessions) var defPolicy = new AuthorizationPolicyBuilder(); defPolicy.Requirements.Add(new UserGroupPermissionRequirement("null", "null")); options.DefaultPolicy = defPolicy.Build(); foreach (SecurableItem Item in RMuseumSecurableItem.Items) { foreach (SecurableItemOperation Operation in Item.Operations) { options.AddPolicy($"{Item.ShortName}:{Operation.ShortName}", policy => policy.Requirements.Add(new UserGroupPermissionRequirement(Item.ShortName, Operation.ShortName))); } } }); // Register the Swagger generator, defining 1 or more Swagger documents services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "RMuseum API", Version = "v1", Description = "RMuseum API", TermsOfService = new Uri("https://ganjoor.net/contact"), Contact = new OpenApiContact { Name = "Ganjoor", Email = "ganjoor@ganjoor.net", Url = new Uri("https://ganjoor.net") } } ); c.EnableAnnotations(); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "RSecurityBackend.xml")); c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme() { Description = "format: \"bearer {token}\"", In = ParameterLocation.Header, Name = "Authorization", Type = SecuritySchemeType.ApiKey }); c.OperationFilter(); c.OperationFilter(); // Adds "(Auth)" to the summary so that you can see which endpoints have Authorization // or use the generic method, e.g. c.OperationFilter>(); }); //IHttpContextAccessor services.TryAddSingleton(); //authorization handler services.AddScoped(); //security context maps to main db context services.AddTransient, RMuseumDbContext>(); //captcha service services.AddTransient(); //generic image file service services.AddTransient(); //app user services services.AddTransient(); //user groups services services.AddTransient(); //audit service services.AddTransient(); //user permission checker services.AddTransient(); //secret generator services.AddTransient(); // email service services.AddTransient(); services.Configure(Configuration); //picture file service services.AddTransient(); //messaging service services.AddTransient(); //artifact service services.AddTransient(); //audio service services.AddTransient(); //ganjoor service services.AddTransient(); //upload limit for IIS services.Configure(options => { options.MaxRequestBodySize = int.Parse(Configuration.GetSection("IIS")["UploadLimit"]); }); services.AddHostedService(); services.AddSingleton(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); // Enable middleware to serve generated Swagger as a JSON endpoint. app.UseSwagger(); // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), // specifying the Swagger JSON endpoint. app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "RMuseum API V1"); c.RoutePrefix = string.Empty; }); app.UseAuthentication(); // global policy - assign here or on each controller app.UseCors("GanjoorCorsPolicy"); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }