📜 ⬆️ ⬇️

ASP.NET Identity Caché Provider - we work with Identity through InterSystems Caché

With the advent of Microsoft's .NET ASP.NET Identity technology, developers have increasingly begun to use it when creating web applications. For a brief insight into the technology, we suggest reading the article . This technology is present in the standard project template and allows you to use the standard implementation of user authentication and authentication functionality.

image


Out of the box, the data provider for ASP.NET Identity is MSSQL, but since the Identity authorization system can interact with any other relational DBMS, we researched and implemented this feature for InterSystems Caché.

First, what is all this for? Imagine that your project uses a Caché DBMS on .NET and you need a complete and reliable authorization system. It is extremely inexpedient to write such a system from scratch, naturally, that you want to use the existing analog in .NET - ASP.NET Identity. But in its pure form, the framework is able to work only with its native Microsoft DBMS - MS SQL. Our task was to implement an adapter that allows easy movement of the Identity to DBMS Intersystems Cache. The task was implemented in ASP.NET Identity Caché Provider.
The core of the ASP.NET Identity Caché Provider project is to implement the Caché Data Provider for ASP.NET Idenity. The main task was to store and provide access to the AspNetRoles , AspNetUserClaims , AspNetUserLogins , AspNetUserRoles and AspNetUsers tables without disrupting the standard logic of working with these tables.
')
A couple of words about ASP.NET Identity architecture
The key objects in Asp.Net Identity are users and roles. All the functionality to create and delete users, interact with the user repository is stored in the UserManager class. The RoleManager class is defined for working with roles and their management in Asp.Net Identity. Below is a Microsoft.AspNet.Identity.Core class diagram.

image



Each user for UserManager provides a IUser interface object . In this case, all user management operations are performed through the storage provided by the IUserStore object. Each role represents an implementation of the IRole interface, and manipulations with roles (add, change, delete) are performed via the RoleManager. Directly implementing the IUser , IRole , IUserStore and IRoleStore interfaces provides the Microsoft.AspNet.Identity EntityFramework namespace, where classes such as IdentityUser , UserStore , IdentityRole , RoleStore , IdentityDbContext are available for use.
image


If you need to store additional information about the user, which is not in the specified tables by default, there is a class IdentityUserClaim (stamps) that allows you to add the necessary fields and then use them, for example, when registering a user.

Let us consider the implementation of the Caché Data Provider for ASP.NET Identity. It took place in two stages:

- Implementation of data storage classes (which will be responsible for state storage) and the IdentityDbContext class, which encapsulates all the low-level logic of working with the data storage. The IdentityDbInitializer class has also been implemented, which adapts the Caché database to work with Identity.
- Implementation of the UserStore and RoleStore classes (together with the integration
tests). Demonstration project.

During the first stage the following classes were implemented:
- IdentityUser - implementation of the IUser interface.
- IdentityUserRole - associative entity for User – Role communication.
- IdentityUserLogin - data about user logins.

An extensible version of the UserLoginInfo class.
- IdentityUserClaim - data on user stamps.
- IdentityDbContext <TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim> is the context of the Entity Framework database.

Consider in more detail the essence of IdentityUser , which is a repository for users, roles, logins, stamps and user-role connections. An example of the implementation of the usual and generic version of IdentityUser .

namespace InterSystems.AspNet.Identity.Cache { /// <summary> /// IUser implementation /// </summary> public class IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser { /// <summary> /// Constructor which creates a new Guid for the Id /// </summary> public IdentityUser() { Id = Guid.NewGuid().ToString(); } /// <summary> /// Constructor that takes a userName /// </summary> /// <param name="userName"></param> public IdentityUser(string userName) : this() { UserName = userName; } } /// <summary> /// IUser implementation /// </summary> /// <typeparam name="TKey"></typeparam> /// <typeparam name="TLogin"></typeparam> /// <typeparam name="TRole"></typeparam> /// <typeparam name="TClaim"></typeparam> public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey> where TLogin : IdentityUserLogin<TKey> where TRole : IdentityUserRole<TKey> where TClaim : IdentityUserClaim<TKey> { /// <summary> /// Constructor /// </summary> public IdentityUser() { Claims = new List<TClaim>(); Roles = new List<TRole>(); Logins = new List<TLogin>(); } /// <summary> /// Email /// </summary> public virtual string Email { get; set; } 

To implement the restriction of access rights in Identity are special objects - Roles. The role in the configuration may correspond to the positions or activities of different user groups.

 namespace InterSystems.AspNet.Identity.Cache { /// <summary> /// EntityType that represents a user belonging to a role /// </summary> public class IdentityUserRole : IdentityUserRole<string> { } /// <summary> /// EntityType that represents a user belonging to a role /// </summary> /// <typeparam name="TKey"></typeparam> public class IdentityUserRole<TKey> { /// <summary> /// UserId for the user that is in the role /// </summary> public virtual TKey UserId { get; set; } /// <summary> /// RoleId for the role /// </summary> public virtual TKey RoleId { get; set; } } } 

IdentityDbContext - an entity that encapsulates the creation of a connection, the loading of entities from the database, the validation of the correspondence of user objects to the structure of related tables and field values. As an example, consider the OnModelCreating method, which validates tables in accordance with the requirements of the Identity.

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { // Mapping and configuring identity entities according to the Cache tables var user = modelBuilder.Entity<TUser>() .ToTable("AspNetUsers"); user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId); user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId); user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId); user.Property(u => u.UserName) .IsRequired() .HasMaxLength(256) .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("UserNameIndex") { IsUnique = true })); user.Property(u => u.Email).HasMaxLength(256); modelBuilder.Entity<TUserRole>() .HasKey(r => new { r.UserId, r.RoleId }) .ToTable("AspNetUserRoles"); modelBuilder.Entity<TUserLogin>() .HasKey(l => new { l.LoginProvider, l.ProviderKey, l.UserId }) .ToTable("AspNetUserLogins"); modelBuilder.Entity<TUserClaim>() .ToTable("AspNetUserClaims"); var role = modelBuilder.Entity<TRole>() .ToTable("AspNetRoles"); role.Property(r => r.Name) .IsRequired() .HasMaxLength(256) .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("RoleNameIndex") { IsUnique = true })); role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId); } 

DbModelBuilder is used to compare CLR classes with a database schema. This code-oriented approach to building an EDM model is called Code First. DbModelBuilder is commonly used to tune a model by overriding OnModelCreating (DbModelBuilder) . However, DbModelBuilder can also be used independently of DbContext to build a model and then construct a DbContext or ObjectContext .

The IdentityDbInitializer class prepares the Caché database for Identity use.

 public void InitializeDatabase(DbContext context) { using (var connection = BuildConnection(context)) { var tables = GetExistingTables(connection); CreateTableIfNotExists(tables, AspNetUsers, connection); CreateTableIfNotExists(tables, AspNetRoles, connection); CreateTableIfNotExists(tables, AspNetUserRoles, connection); CreateTableIfNotExists(tables, AspNetUserClaims, connection); CreateTableIfNotExists(tables, AspNetUserLogins, connection); CreateIndexesIfNotExist(connection); } } 

The CreateTableIfNotExists methods create the necessary tables, if they do not already exist. Checking the existence of a table is done by querying the Cache table - Dictionary.CompiledClass , which stores information about already existing tables. If a table has not yet been created, it is created.

At the second stage, such entities as IdentityUserStore and IdentityRoleStore, which encapsulate the logic of adding, editing and deleting users, and roles were implemented. These entities required 100% coverage with unit tests.

To summarize: a data provider was implemented for Caché DBMS with the Entity Framework in the context of ASP.NET Identity technology. The application is designed in a separate Nuget-package, and now, if you need to work with the Caché DBMS, and at the same time use the standard Microsoft authorization, you simply integrate the Identity Caché Provider assembly into the project through the Nuget Package Manager.

The implementation of the project with the source code, an example and tests is laid out on GitHub .

Source: https://habr.com/ru/post/301368/


All Articles