Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Куршев, Володько, Хрусталев #31

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions IdentityServer/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,32 @@ public static class Config
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{
new ApiResource("api1", "My API")
new ApiResource("photos_service", "Сервис фотографий")
{
Scopes = { "scope1" }
Scopes = { "photos" }
}

};

public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("scope1", "My scope")
new ApiScope("photos", "Фотографии")
};

public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "client",

// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,

// secret for authentication
ClientId = "Photos App by OAuth",
ClientSecrets =
{
new Secret("secret".Sha256())
},

// scopes that client has access to
AllowedScopes = { "scope1" }
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = { "photos" }
}
};
}
Expand Down
14 changes: 14 additions & 0 deletions PhotosApp/Areas/Identity/Data/PhotosAppUser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;

namespace PhotosApp.Areas.Identity.Data
{
// Add profile data for application users by adding properties to the PhotosAppUser class
public class PhotosAppUser : IdentityUser
{
public bool Paid { get; set; }
}
}
27 changes: 27 additions & 0 deletions PhotosApp/Areas/Identity/Data/UsersDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using PhotosApp.Areas.Identity.Data;

namespace PhotosApp.Areas.Identity.Data
{
public class UsersDbContext : IdentityDbContext<PhotosAppUser>
{
public UsersDbContext(DbContextOptions<UsersDbContext> options)
: base(options)
{
}

protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
}
147 changes: 147 additions & 0 deletions PhotosApp/Areas/Identity/IdentityHostingStartup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using PhotosApp.Areas.Identity.Data;
using PhotosApp.Services;
using PhotosApp.Services.Authorization;
using PhotosApp.Services.TicketStores;

[assembly: HostingStartup(typeof(PhotosApp.Areas.Identity.IdentityHostingStartup))]
namespace PhotosApp.Areas.Identity
{
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
services.AddDbContext<UsersDbContext>(options =>
options.UseSqlite(
context.Configuration.GetConnectionString("UsersDbContextConnection")));
services.AddDbContext<TicketsDbContext>(options =>
options.UseSqlite(
context.Configuration.GetConnectionString("TicketsDbContextConnection")));

services.Configure<IdentityOptions>(options =>
{
// Default Password settings.
options.Password.RequireDigit = false;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
});

services.Configure<IdentityOptions>(options =>
{
// Default SignIn settings.
options.SignIn.RequireConfirmedEmail = false;
options.SignIn.RequireConfirmedPhoneNumber = false;
options.SignIn.RequireConfirmedAccount = false;
});

services.Configure<PasswordHasherOptions>(options =>
{
options.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV3;
options.IterationCount = 12000;
});

services.AddScoped<IPasswordHasher<PhotosAppUser>, SimplePasswordHasher<PhotosAppUser>>();
services.AddScoped<IAuthorizationHandler, MustOwnPhotoHandler>();

services.AddDefaultIdentity<PhotosAppUser>()
.AddRoles<IdentityRole>()
.AddClaimsPrincipalFactory<CustomClaimsPrincipalFactory>()
.AddPasswordValidator<UsernameAsPasswordValidator<PhotosAppUser>>()
.AddErrorDescriber<RussianIdentityErrorDescriber>()
.AddEntityFrameworkStores<UsersDbContext>();

services.AddTransient<EntityTicketStore>();

services.ConfigureApplicationCookie(options =>
{
var serviceProvider = services.BuildServiceProvider();
options.SessionStore = serviceProvider.GetRequiredService<EntityTicketStore>();

options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.Cookie.Name = "PhotosApp.Auth";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.LoginPath = "/Identity/Account/Login";
// ReturnUrlParameter requires
//using Microsoft.AspNetCore.Authentication.Cookies;
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;

});
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme,
IdentityConstants.ApplicationScheme)
.RequireAuthenticatedUser()
.Build();
options.AddPolicy(
"Beta",
policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.RequireClaim("testing", "beta");
});
options.AddPolicy(
"CanAddPhoto",
policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.RequireClaim("subscription", "paid");
});
options.AddPolicy(
"MustOwnPhoto",
policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(new MustOwnPhotoRequirement());
});
options.AddPolicy("Dev", policyBuilder =>
{
policyBuilder.RequireRole("Dev");
policyBuilder.AddAuthenticationSchemes(
IdentityConstants.ApplicationScheme,
JwtBearerDefaults.AuthenticationScheme);
});
});
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidateIssuerSigningKey = true,
IssuerSigningKey = TemporaryTokens.SigningKey
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = c =>
{
c.Token = c.Request.Cookies[TemporaryTokens.CookieName];
return Task.CompletedTask;
}
};
});
});
}
}
}
10 changes: 10 additions & 0 deletions PhotosApp/Areas/Identity/Pages/Account/AccessDenied.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@page
@model AccessDeniedModel
@{
ViewData["Title"] = "Access denied";
}

<header>
<h1 class="text-danger">@ViewData["Title"]</h1>
<p class="text-danger">You do not have access to this resource.</p>
</header>
17 changes: 17 additions & 0 deletions PhotosApp/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace PhotosApp.Areas.Identity.Pages.Account
{
public class AccessDeniedModel : PageModel
{
public void OnGet()
{

}
}
}

7 changes: 7 additions & 0 deletions PhotosApp/Areas/Identity/Pages/Account/ConfirmEmail.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@page
@model ConfirmEmailModel
@{
ViewData["Title"] = "Confirm email";
}

<h1>@ViewData["Title"]</h1>
47 changes: 47 additions & 0 deletions PhotosApp/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using PhotosApp.Areas.Identity.Data;

namespace PhotosApp.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ConfirmEmailModel : PageModel
{
private readonly UserManager<PhotosAppUser> _userManager;

public ConfirmEmailModel(UserManager<PhotosAppUser> userManager)
{
_userManager = userManager;
}

[TempData]
public string StatusMessage { get; set; }

public async Task<IActionResult> OnGetAsync(string userId, string code)
{
if (userId == null || code == null)
{
return RedirectToPage("/Index");
}

var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}

code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ConfirmEmailAsync(user, code);
StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
return Page();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@page
@model ConfirmEmailChangeModel
@{
ViewData["Title"] = "Confirm email change";
}

<h1>@ViewData["Title"]</h1>
<partial name="_StatusMessage" model="Model.StatusMessage" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using PhotosApp.Areas.Identity.Data;

namespace PhotosApp.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ConfirmEmailChangeModel : PageModel
{
private readonly UserManager<PhotosAppUser> _userManager;
private readonly SignInManager<PhotosAppUser> _signInManager;

public ConfirmEmailChangeModel(UserManager<PhotosAppUser> userManager, SignInManager<PhotosAppUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}

[TempData]
public string StatusMessage { get; set; }

public async Task<IActionResult> OnGetAsync(string userId, string email, string code)
{
if (userId == null || email == null || code == null)
{
return RedirectToPage("/Index");
}

var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}

code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ChangeEmailAsync(user, email, code);
if (!result.Succeeded)
{
StatusMessage = "Error changing email.";
return Page();
}

// In our UI email and user name are one and the same, so when we update the email
// we need to update the user name.
var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
if (!setUserNameResult.Succeeded)
{
StatusMessage = "Error changing user name.";
return Page();
}

await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Thank you for confirming your email change.";
return Page();
}
}
}
Loading