diff --git a/IoTSharp.Hub/Controllers/AccountController.cs b/IoTSharp.Hub/Controllers/AccountController.cs index 3603a34560902e7a33d2fe076c78992ab940f06b..6f2c8281f13e869479a223c520fc0337c4d0a0f0 100644 --- a/IoTSharp.Hub/Controllers/AccountController.cs +++ b/IoTSharp.Hub/Controllers/AccountController.cs @@ -1,4 +1,7 @@ using IoTSharp.Hub.Data; +using IoTSharp.Hub.Extensions; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -41,27 +44,35 @@ namespace IoTSharp.Hub.Controllers [AllowAnonymous] [HttpPost] - public async Task Login([FromBody] LoginDto model) + public async Task> Login([FromBody] LoginDto model) { - IActionResult actionResult = NoContent(); try { + var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, false); if (result.Succeeded) { var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email); - actionResult = Ok(new { code = 0, msg = "OK", data = GenerateJwtToken(model.Email, appUser) }); + var token = await _userManager.GenerateJwtTokenAsync(appUser); + return Ok(new LoginResult() + { + Succeeded= result.Succeeded, + Token = token, + UserName = appUser.UserName, + SignIn = result + }); } else { - actionResult = BadRequest(new { code = -3, msg = "Login Error", data = result }); + + return Unauthorized(new { code = ApiCode.LoginError, msg = "Unauthorized", data = result }); } } catch (Exception ex) { - actionResult = BadRequest(new { code = -1, msg = ex.Message, data = ex }); + return this.ExceptionRequest(ex); } - return actionResult; + } /// @@ -71,9 +82,9 @@ namespace IoTSharp.Hub.Controllers /// [AllowAnonymous] [HttpPost] - public async Task Register([FromBody] RegisterDto model) + public async Task> Register([FromBody] RegisterDto model) { - IActionResult actionResult = NoContent(); + ActionResult actionResult = NoContent(); try { var user = new IdentityUser @@ -86,13 +97,14 @@ namespace IoTSharp.Hub.Controllers { await _signInManager.SignInAsync(user, false); await _signInManager.UserManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, model.Email)); - var cust = _context.Customer.FirstOrDefault(c => c.Name == model.CustomerName); - - if (cust != null) + var customer = _context.Customer.FirstOrDefault(c => c.Name == model.CustomerName); + if (customer != null) { - await _signInManager.UserManager.AddClaimAsync(user, new Claim(ClaimTypes.GroupSid, cust.Id.ToString())); - - actionResult = Ok(new { code = 0, msg = "OK", data = GenerateJwtToken(model.Email, user) }); + await _signInManager.UserManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, model.Email)); + await _signInManager.UserManager.AddClaimAsync(user, new Claim(IoTSharpClaimTypes.Customer, customer.Id.ToString())); + await _signInManager.UserManager.AddClaimAsync(user, new Claim(IoTSharpClaimTypes.Tenant, customer.Tenant.Id.ToString())); + await _signInManager.UserManager.AddToRolesAsync(user, new[] { nameof(UserRole.NormalUser) }); + actionResult = CreatedAtAction(nameof(this.Login), new LoginDto() { Email = model.Email, Password = model.Password }); } } else @@ -109,31 +121,14 @@ namespace IoTSharp.Hub.Controllers return actionResult; } - - private object GenerateJwtToken(string email, IdentityUser user) + public class LoginResult { - var claims = new List - { - new Claim(JwtRegisteredClaimNames.Sub, email), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - new Claim(ClaimTypes.NameIdentifier, user.Id) - }; - - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"])); - var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); - var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpireDays"])); - - var token = new JwtSecurityToken( - _configuration["JwtIssuer"], - _configuration["JwtIssuer"], - claims, - expires: expires, - signingCredentials: creds - ); - - return new JwtSecurityTokenHandler().WriteToken(token); + + public string Token { get; set; } + public string UserName { get; set; } + public Microsoft.AspNetCore.Identity.SignInResult SignIn { get; set; } + public bool Succeeded { get; set; } } - public class LoginDto { [Required] diff --git a/IoTSharp.Hub/Controllers/CustomersController.cs b/IoTSharp.Hub/Controllers/CustomersController.cs index 005059a5b8ac1f84695c75ba4c67731812ee7853..727e6191d095f0fc6f46557cea9de9567464ef14 100644 --- a/IoTSharp.Hub/Controllers/CustomersController.cs +++ b/IoTSharp.Hub/Controllers/CustomersController.cs @@ -82,7 +82,7 @@ namespace IoTSharp.Hub.Controllers customer.Tenant= _context.Tenant.Find(customer.Tenant.Id); _context.Customer.Add(customer); await _context.SaveChangesAsync(); - return await GetCustomer(customer.Id); + return CreatedAtAction("GetCustomer",customer.Id); } // DELETE: api/Customers/5 diff --git a/IoTSharp.Hub/Controllers/InstallerController.cs b/IoTSharp.Hub/Controllers/InstallerController.cs index 01e3af26dd37dc1b0ba82a1d869512121a50f7c1..a45fbf075c78f63491498227b0bbc8f0a968eaec 100644 --- a/IoTSharp.Hub/Controllers/InstallerController.cs +++ b/IoTSharp.Hub/Controllers/InstallerController.cs @@ -1,4 +1,5 @@ using IoTSharp.Hub.Data; +using IoTSharp.Hub.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -22,109 +23,71 @@ namespace IoTSharp.Hub.Controllers private readonly UserManager _userManager; private readonly IConfiguration _configuration; private readonly SignInManager _signInManager; - + private readonly ApplicationDBInitializer _dBInitializer; public InstallerController( UserManager userManager, SignInManager signInManager, IConfiguration configuration, ILogger logger, ApplicationDbContext context - ) + , ApplicationDBInitializer dBInitializer) { _userManager = userManager; _signInManager = signInManager; _configuration = configuration; _logger = logger; _context = context; + _dBInitializer = dBInitializer; } [AllowAnonymous] - [HttpPost] - public IActionResult CheckInstallation() + [HttpGet] + public ActionResult Instance() { - int code = -1; - string msg; - bool installed = false; try { - installed = !(_context.Tenant.Count() == 0 && _context.Customer.Count() == 0); - msg = installed ? "Already installed" : "Not Install"; - code = 0; + return base.Ok(GetInstanceDto()); } catch (Exception ex) { - msg = ex.Message; + return this.ExceptionRequest(ex); } - return Ok(new - { - code, - msg, - data = new { installed } - }); + + } + + private InstanceDto GetInstanceDto() + { + return new InstanceDto() { Installed = _context.Relationship.Any(), Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() }; } [AllowAnonymous] [HttpPost] - public async Task Install([FromBody] InstallDto model) + public ActionResult Install([FromBody] InstallDto model) { - IActionResult actionResult = NoContent(); - var tran = _context.Database.BeginTransaction(); + ActionResult actionResult = NoContent(); try { - if (_context.Tenant.Count() == 0 && _context.Customer.Count() == 0) + if (!_context.Relationship.Any()) { - var tenant = new Tenant() { Id = Guid.NewGuid(), Name = model.TenantName, EMail = model.TenantEMail }; - var customer = new Customer() { Id = Guid.NewGuid(), Name = model.CustomerName, Email = model.CustomerEMail }; - customer.Tenant = tenant; - tenant.Customers = new List(); - tenant.Customers.Add(customer); - var user = new IdentityUser - { - Email = model.Email, - UserName = model.Email, - PhoneNumber = model.PhoneNumber - }; - - var result = await _userManager.CreateAsync(user, model.Password); - - if (result.Succeeded) - { - _context.Tenant.Add(tenant); - _context.Customer.Add(customer); - await _signInManager.SignInAsync(user, false); - await _signInManager.UserManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, model.Email)); - await _signInManager.UserManager.AddClaimAsync(user, new Claim(ClaimTypes.GroupSid, customer.Id.ToString())); - - - var rship = new Relationship(); - rship.IdentityUser = _context.Users.Find(user.Id); - rship.Customer = customer; - rship.Tenant = tenant; - _context.Add(rship); - int savechangesresult = _context.SaveChanges(); - tran.Commit(); - actionResult = Ok(new { code = 0, msg = "OK", data = new { result = savechangesresult >= 0, count = savechangesresult } }); - } - else - { - tran.Rollback(); - var msg = from e in result.Errors select $"{e.Code}:{e.Description}\r\n"; - actionResult = BadRequest(new { code = -3, msg = string.Join(';', msg.ToArray()) }); - } + _dBInitializer.SeedRole(); + _dBInitializer.SeedUser(model); + actionResult = Ok(GetInstanceDto()); } else { - tran.Rollback(); - actionResult = Ok(new { code = 1, msg = "Already installed" }); + actionResult= BadRequest(new { code = ApiCode.AlreadyExists, msg = "Already installed", data = GetInstanceDto() }); } } catch (Exception ex) { - tran.Rollback(); - actionResult = BadRequest(new { code = -2, msg = ex.Message, data = ex }); - _logger.LogError(ex, ex.Message); + actionResult= this.ExceptionRequest(ex); } return actionResult; } + public class InstanceDto + { + public string Version { get; internal set; } + public bool Installed { get; internal set; } + } public class InstallDto { [Required] diff --git a/IoTSharp.Hub/Controllers/TenantsController.cs b/IoTSharp.Hub/Controllers/TenantsController.cs index 0fa9b9a2a4c9b0f023b95053ce5a716cc1a5d9a4..c8a37e3fe23a4e3a7f62c975fd26c9e7655c4115 100644 --- a/IoTSharp.Hub/Controllers/TenantsController.cs +++ b/IoTSharp.Hub/Controllers/TenantsController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using IoTSharp.Hub.Data; +using Microsoft.AspNetCore.Authorization; namespace IoTSharp.Hub.Controllers { @@ -19,7 +20,8 @@ namespace IoTSharp.Hub.Controllers { _context = context; } - + [Authorize(Roles =nameof(UserRole.TenantAdmin))] + //[AllowAnonymous] // GET: api/Tenants [HttpGet] public async Task>> GetTenant() @@ -27,7 +29,7 @@ namespace IoTSharp.Hub.Controllers return await _context.Tenant.ToListAsync(); } - + [Authorize(Roles = nameof(UserRole.NormalUser))] // GET: api/Tenants/5 [HttpGet("{id}")] public async Task> GetTenant(Guid id) @@ -41,7 +43,7 @@ namespace IoTSharp.Hub.Controllers return tenant; } - + [Authorize(Roles = nameof(UserRole.TenantAdmin))] // PUT: api/Tenants/5 [HttpPut("{id}")] public async Task PutTenant(Guid id, Tenant tenant) @@ -71,7 +73,7 @@ namespace IoTSharp.Hub.Controllers return NoContent(); } - + [Authorize(Roles = nameof(UserRole.SystemAdmin))] // POST: api/Tenants [HttpPost] public async Task> PostTenant(Tenant tenant) @@ -81,7 +83,7 @@ namespace IoTSharp.Hub.Controllers return CreatedAtAction("GetTenant", new { id = tenant.Id }, tenant); } - + [Authorize(Roles = nameof(UserRole.SystemAdmin))] // DELETE: api/Tenants/5 [HttpDelete("{id}")] public async Task> DeleteTenant(Guid id) diff --git a/IoTSharp.Hub/Data/ApplicationDBInitializer.cs b/IoTSharp.Hub/Data/ApplicationDBInitializer.cs new file mode 100644 index 0000000000000000000000000000000000000000..1b332d744ea89c11ef3e37a384b4542bb8851f4c --- /dev/null +++ b/IoTSharp.Hub/Data/ApplicationDBInitializer.cs @@ -0,0 +1,101 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using static IoTSharp.Hub.Controllers.InstallerController; + +namespace IoTSharp.Hub.Data +{ + public class ApplicationDBInitializer + { + + private readonly RoleManager _role; + + private ApplicationDbContext _context; + private ILogger _logger; + private readonly UserManager _userManager; + private readonly IConfiguration _configuration; + private readonly SignInManager _signInManager; + public ApplicationDBInitializer( + UserManager userManager, + SignInManager signInManager, + IConfiguration configuration, ILogger logger, + ApplicationDbContext context, RoleManager role + ) + { + _userManager = userManager; + _signInManager = signInManager; + _configuration = configuration; + _logger = logger; + _context = context; + _role = role; + } + public void SeedRole() + { + var roles = new IdentityRole[]{new IdentityRole(nameof(UserRole.Anonymous)) + , new IdentityRole(nameof(UserRole.NormalUser)) + , new IdentityRole(nameof(UserRole.CustomerAdmin)) + , new IdentityRole(nameof(UserRole.TenantAdmin)) + , new IdentityRole(nameof(UserRole.SystemAdmin)) }; + roles.ToList().ForEach(role => + { + _role.CreateAsync(role); + _role.UpdateNormalizedRoleNameAsync(role); + }); + _context.SaveChanges(); + } + public void SeedUser(InstallDto model) + { + var tenant = _context.Tenant.FirstOrDefault(t => t.EMail == model.TenantEMail); + var customer = _context.Customer.FirstOrDefault(t => t.Email == model.CustomerEMail); + if (tenant == null && customer == null) + { + tenant = new Tenant() { Id = Guid.NewGuid(), Name = model.TenantName, EMail = model.TenantEMail }; + customer = new Customer() { Id = Guid.NewGuid(), Name = model.CustomerName, Email = model.CustomerEMail }; + customer.Tenant = tenant; + tenant.Customers = new List(); + tenant.Customers.Add(customer); + _context.Tenant.Add(tenant); + _context.Customer.Add(customer); + _context.SaveChanges(); + } + IdentityUser user = _userManager.FindByEmailAsync(model.Email).GetAwaiter().GetResult(); + if (user == null) + { + user = new IdentityUser + { + Email = model.Email, + UserName = model.Email, + PhoneNumber = model.PhoneNumber + }; + var result = _userManager.CreateAsync(user, model.Password).GetAwaiter().GetResult(); + if (result.Succeeded) + { + _signInManager.SignInAsync(user, false); + _signInManager.UserManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, model.Email)); + _signInManager.UserManager.AddClaimAsync(user, new Claim(IoTSharpClaimTypes.Customer, customer.Id.ToString())); + _signInManager.UserManager.AddClaimAsync(user, new Claim(IoTSharpClaimTypes.Tenant, tenant.Id.ToString())); + _signInManager.UserManager.AddToRolesAsync(user, new[] { + nameof(UserRole.Anonymous), + nameof(UserRole.NormalUser), + nameof(UserRole.CustomerAdmin), + nameof(UserRole.TenantAdmin), + nameof(UserRole.SystemAdmin)}); + } + } + var rship = new Relationship + { + IdentityUser = _context.Users.Find(user.Id), + Customer = _context.Customer.Find(customer.Id), + Tenant = _context.Tenant.Find(tenant.Id) + }; + _context.Add(rship); + _context.SaveChanges(); + } + } +} diff --git a/IoTSharp.Hub/Data/ApplicationDbContext.cs b/IoTSharp.Hub/Data/ApplicationDbContext.cs index 0db76944a361dfff170f94a0f89d33cfcf606baf..0081b947ac6a5e29a97b6738214e4b21970067b0 100644 --- a/IoTSharp.Hub/Data/ApplicationDbContext.cs +++ b/IoTSharp.Hub/Data/ApplicationDbContext.cs @@ -24,6 +24,7 @@ namespace IoTSharp.Hub.Data { Database.Migrate(); } + } public DatabaseType DatabaseType { get; private set; } @@ -44,8 +45,9 @@ namespace IoTSharp.Hub.Data modelBuilder.Entity().HasDiscriminator(nameof(Data.DataStorage.Catalog)); modelBuilder.Entity().HasDiscriminator(nameof(Data.DataStorage.Catalog)); modelBuilder.Entity().HasDiscriminator(nameof(Data.DataStorage.Catalog)); + + - switch (DatabaseType) { @@ -65,6 +67,7 @@ namespace IoTSharp.Hub.Data } } + private void ForNpgsql(ModelBuilder modelBuilder) { modelBuilder.Entity() diff --git a/IoTSharp.Hub/Data/Enums.cs b/IoTSharp.Hub/Data/Enums.cs index 5d1334bc556c89189a091d4e9538d97ce3aa2935..ea9b56a9f47fd7d2174c01ea7289639ad060b7a2 100644 --- a/IoTSharp.Hub/Data/Enums.cs +++ b/IoTSharp.Hub/Data/Enums.cs @@ -1,11 +1,25 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; namespace IoTSharp.Hub.Data { - + public static class IoTSharpClaimTypes + { + + public const string Customer = "http://schemas.iotsharp.net/ws/2019/01/identity/claims/customer"; + public const string Tenant = "http://schemas.iotsharp.net/ws/2019/01/identity/claims/tenant"; + } + public enum ApiCode:int + { + Success= 10000, + LoginError=10001, + Exception = 10002, + AlreadyExists = 10003, + } public enum DataCatalog { None, @@ -14,6 +28,15 @@ namespace IoTSharp.Hub.Data TelemetryData, TelemetryLatest, + } + public enum UserRole + { + Anonymous, + NormalUser, + CustomerAdmin, + TenantAdmin, + SystemAdmin, + } public enum DataType diff --git a/IoTSharp.Hub/Data/Migrations/20190108071914_AddRole.Designer.cs b/IoTSharp.Hub/Data/Migrations/20190108071914_AddRole.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..dd542aaeb500d9537828644f54dbc46faba750c8 --- /dev/null +++ b/IoTSharp.Hub/Data/Migrations/20190108071914_AddRole.Designer.cs @@ -0,0 +1,481 @@ +// +using System; +using IoTSharp.Hub.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace IoTSharp.Hub.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20190108071914_AddRole")] + partial class AddRole + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .HasAnnotation("ProductVersion", "2.2.0-rtm-35687") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("IoTSharp.Hub.Data.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Address"); + + b.Property("City"); + + b.Property("Country"); + + b.Property("Email"); + + b.Property("Name"); + + b.Property("Phone"); + + b.Property("Province"); + + b.Property("Street"); + + b.Property("TenantId"); + + b.Property("ZipCode"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("Customer"); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.DataStorage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Catalog"); + + b.Property("DateTime"); + + b.Property("KeyName") + .IsRequired(); + + b.Property("Type"); + + b.Property("Value_Binary"); + + b.Property("Value_Boolean"); + + b.Property("Value_Double"); + + b.Property("Value_Json") + .HasColumnType("jsonb"); + + b.Property("Value_Long"); + + b.Property("Value_String"); + + b.Property("Value_XML") + .HasColumnType("xml"); + + b.HasKey("Id"); + + b.ToTable("DataStorage"); + + b.HasDiscriminator("Catalog").HasValue(0); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CustomerId"); + + b.Property("Name"); + + b.Property("TenantId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("TenantId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.Relationship", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CustomerId"); + + b.Property("IdentityUserId"); + + b.Property("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("IdentityUserId"); + + b.HasIndex("TenantId"); + + b.ToTable("Relationship"); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Address"); + + b.Property("City"); + + b.Property("Country"); + + b.Property("EMail"); + + b.Property("Name"); + + b.Property("Phone"); + + b.Property("Province"); + + b.Property("Street"); + + b.Property("ZipCode"); + + b.HasKey("Id"); + + b.ToTable("Tenant"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.AttributeData", b => + { + b.HasBaseType("IoTSharp.Hub.Data.DataStorage"); + + b.Property("DeviceId"); + + b.HasIndex("DeviceId"); + + b.HasDiscriminator().HasValue(1); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.AttributeLatest", b => + { + b.HasBaseType("IoTSharp.Hub.Data.DataStorage"); + + b.Property("DeviceId") + .HasColumnName("AttributeLatest_DeviceId"); + + b.HasIndex("DeviceId"); + + b.HasDiscriminator().HasValue(2); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.TelemetryData", b => + { + b.HasBaseType("IoTSharp.Hub.Data.DataStorage"); + + b.Property("DeviceId") + .HasColumnName("TelemetryData_DeviceId"); + + b.HasIndex("DeviceId"); + + b.HasDiscriminator().HasValue(3); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.TelemetryLatest", b => + { + b.HasBaseType("IoTSharp.Hub.Data.DataStorage"); + + b.Property("DeviceId") + .HasColumnName("TelemetryLatest_DeviceId"); + + b.HasIndex("DeviceId"); + + b.HasDiscriminator().HasValue(4); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.Customer", b => + { + b.HasOne("IoTSharp.Hub.Data.Tenant", "Tenant") + .WithMany("Customers") + .HasForeignKey("TenantId"); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.Device", b => + { + b.HasOne("IoTSharp.Hub.Data.Customer", "Customer") + .WithMany("Devices") + .HasForeignKey("CustomerId"); + + b.HasOne("IoTSharp.Hub.Data.Tenant", "Tenant") + .WithMany("Devices") + .HasForeignKey("TenantId"); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.Relationship", b => + { + b.HasOne("IoTSharp.Hub.Data.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId"); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "IdentityUser") + .WithMany() + .HasForeignKey("IdentityUserId"); + + b.HasOne("IoTSharp.Hub.Data.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.AttributeData", b => + { + b.HasOne("IoTSharp.Hub.Data.Device", "Device") + .WithMany("AttributeData") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.AttributeLatest", b => + { + b.HasOne("IoTSharp.Hub.Data.Device", "Device") + .WithMany("AttributeLatest") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.TelemetryData", b => + { + b.HasOne("IoTSharp.Hub.Data.Device", "Device") + .WithMany("TelemetryData") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IoTSharp.Hub.Data.TelemetryLatest", b => + { + b.HasOne("IoTSharp.Hub.Data.Device", "Device") + .WithMany("TelemetryLatest") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/IoTSharp.Hub/Data/Migrations/20190108071914_AddRole.cs b/IoTSharp.Hub/Data/Migrations/20190108071914_AddRole.cs new file mode 100644 index 0000000000000000000000000000000000000000..f0a6699164bd4088a102eaa075c603b0ea2af958 --- /dev/null +++ b/IoTSharp.Hub/Data/Migrations/20190108071914_AddRole.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace IoTSharp.Hub.Migrations +{ + public partial class AddRole : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Name", + table: "AspNetUserTokens", + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 128); + + migrationBuilder.AlterColumn( + name: "LoginProvider", + table: "AspNetUserTokens", + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 128); + + migrationBuilder.AlterColumn( + name: "ProviderKey", + table: "AspNetUserLogins", + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 128); + + migrationBuilder.AlterColumn( + name: "LoginProvider", + table: "AspNetUserLogins", + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 128); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Name", + table: "AspNetUserTokens", + maxLength: 128, + nullable: false, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "LoginProvider", + table: "AspNetUserTokens", + maxLength: 128, + nullable: false, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "ProviderKey", + table: "AspNetUserLogins", + maxLength: 128, + nullable: false, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "LoginProvider", + table: "AspNetUserLogins", + maxLength: 128, + nullable: false, + oldClrType: typeof(string)); + } + } +} diff --git a/IoTSharp.Hub/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/IoTSharp.Hub/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 7aceadbae10f61be005e6c9fb097db438fdb75ef..c95aff8703670768a2390156be26a83d120a23fd 100644 --- a/IoTSharp.Hub/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/IoTSharp.Hub/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -273,11 +273,9 @@ namespace IoTSharp.Hub.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.Property("LoginProvider") - .HasMaxLength(128); + b.Property("LoginProvider"); - b.Property("ProviderKey") - .HasMaxLength(128); + b.Property("ProviderKey"); b.Property("ProviderDisplayName"); @@ -308,11 +306,9 @@ namespace IoTSharp.Hub.Migrations { b.Property("UserId"); - b.Property("LoginProvider") - .HasMaxLength(128); + b.Property("LoginProvider"); - b.Property("Name") - .HasMaxLength(128); + b.Property("Name"); b.Property("Value"); diff --git a/IoTSharp.Hub/Extensions/MiscExtension.cs b/IoTSharp.Hub/Extensions/MiscExtension.cs index a4a77f27f0e9313de9d9b91d2c7e7ea4ba33b534..4acf51dc317cb00d2697381d87dcd25bacd4f70f 100644 --- a/IoTSharp.Hub/Extensions/MiscExtension.cs +++ b/IoTSharp.Hub/Extensions/MiscExtension.cs @@ -1,4 +1,9 @@ -using System.Threading.Tasks; +using IoTSharp.Hub.Data; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Threading.Tasks; namespace IoTSharp.Hub.Extensions { @@ -8,5 +13,20 @@ namespace IoTSharp.Hub.Extensions { return Task.CompletedTask; } + public static BadRequestObjectResult ExceptionRequest( this ControllerBase @base,Exception exception) + { + MethodBase mb = new StackTrace(exception).GetFrame(0).GetMethod(); + MethodBase cu = new StackTrace(true).GetFrame(0).GetMethod(); + return @base.BadRequest(new + { + code = ApiCode.Exception, + msg = exception.Message, + data = new + { + ExceptionMethod = mb.DeclaringType.FullName + "." + mb.Name, + MethodName = cu.Name + } + }); + } } } \ No newline at end of file diff --git a/IoTSharp.Hub/Extensions/TokenExtension.cs b/IoTSharp.Hub/Extensions/TokenExtension.cs new file mode 100644 index 0000000000000000000000000000000000000000..e7912ed4a3e8414fde44bfee04a1c016cd1a0355 --- /dev/null +++ b/IoTSharp.Hub/Extensions/TokenExtension.cs @@ -0,0 +1,130 @@ +using IoTSharp.Hub.Data; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace IoTSharp.Hub +{ + public static class TokenExtension + { + static byte[] symmetricKeyBytes; + static SymmetricSecurityKey symmetricKey; + static SigningCredentials signingCredentials; + static TokenValidationParameters tokenValidationParams; + static TimeSpan _expire; + static string _issuer; + static string _audience; + //Construct our JWT authentication paramaters then inject the parameters into the current TokenBuilder instance + // If injecting an RSA key for signing use this method + // Be weary of common jwt trips: https://trustfoundry.net/jwt-hacking-101/ and https://www.sjoerdlangkemper.nl/2016/09/28/attacking-jwt-authentication/ + //public static void ConfigureJwtAuthentication(this IServiceCollection services, RSAParameters rsaParams) + public static void ConfigureJwtAuthentication(this IServiceCollection services, string issuer, string audience, string key, TimeSpan expire) + { + symmetricKeyBytes = Encoding.ASCII.GetBytes(key); + symmetricKey = new SymmetricSecurityKey(symmetricKeyBytes); + signingCredentials = new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256); + _expire = expire; + if (_expire.TotalMinutes < 60) expire = TimeSpan.FromMinutes(60); + _issuer = issuer ?? "iotsharp.net"; + _audience = audience ?? _issuer; + + tokenValidationParams = new TokenValidationParameters() + { + ValidateIssuerSigningKey = true, + ValidIssuer = issuer, + ValidAudience = audience, + ValidateLifetime = true, + ValidateAudience = true, + RequireSignedTokens = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)), + ClockSkew = TimeSpan.Zero + }; + services.AddAuthentication(options => + { + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + + options.TokenValidationParameters = tokenValidationParams; +#if PROD || UAT + options.IncludeErrorDetails = false; +#elif DEBUG + options.RequireHttpsMetadata = false; +#endif + }); + } + public static async Task GenerateJwtTokenAsync(this UserManager manager, IdentityUser user) + { + var roles = await manager.GetRolesAsync(user); + var claims = new List + { + new Claim(JwtRegisteredClaimNames.Sub, user.Email), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(ClaimTypes.NameIdentifier, user.Id) + }; + var lstclaims = await manager.GetClaimsAsync(user); + if (roles != null) + { + foreach (var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + } + claims.AddRange(lstclaims.Where(c => c.Type == IoTSharpClaimTypes.Tenant || c.Type == IoTSharpClaimTypes.Customer).ToList()); + var head = new JwtHeader(); + var jwt = new JwtSecurityToken(tokenValidationParams.ValidIssuer, tokenValidationParams.ValidAudience, claims, DateTime.UtcNow, DateTime.Now.AddMinutes(_expire.TotalMinutes), signingCredentials); + jwt.Payload.AddClaims(claims.ToArray()); + return new JwtSecurityTokenHandler().WriteToken(jwt); + } + public static JwtSecurityToken GetJwtSecurityToken(this HttpRequest httpRequest) + { + JwtSecurityToken result = null; + var authenticationHeaders = httpRequest.Headers["Authorization"].ToArray(); + if ((authenticationHeaders == null) || (authenticationHeaders.Length != 1)) + { + result = null; + } + else + { + var jwToken = authenticationHeaders[0].Split(' ')[1]; + var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); + ClaimsPrincipal principal = null; + SecurityToken securityToken = null; + try + { + principal = jwtSecurityTokenHandler.ValidateToken(jwToken, tokenValidationParams, out securityToken); + } + catch (Exception ex) + { + throw ex; + } + if ((principal != null) && (principal.Claims != null)) + { + result = securityToken as JwtSecurityToken; + Trace.WriteLine(result.Audiences.First()); + Trace.WriteLine(result.Issuer); + } + } + return result; + } + public static string GetUserId(this JwtSecurityToken jwtSecurityToken) + { + return jwtSecurityToken.Payload["userid"] as string; + } + public static string GetPayloadValue(this JwtSecurityToken jwtSecurityToken, string Key) + { + return jwtSecurityToken.Payload[Key] as string; + } + } +} diff --git a/IoTSharp.Hub/IoTSharp.Hub.csproj b/IoTSharp.Hub/IoTSharp.Hub.csproj index 546ef3fd055961dd920e1387d5e391b740bbdce4..c94e7a81ba9584ff023f1b0052d705ca31f53d32 100644 --- a/IoTSharp.Hub/IoTSharp.Hub.csproj +++ b/IoTSharp.Hub/IoTSharp.Hub.csproj @@ -24,12 +24,9 @@ - - - - + @@ -46,7 +43,7 @@ - + diff --git a/IoTSharp.Hub/Startup.cs b/IoTSharp.Hub/Startup.cs index 72229d189e9f7685627fbb04bffb301b39011d32..1c83b9e4b9499a7e0bfe131a6aabfa931022f6e8 100644 --- a/IoTSharp.Hub/Startup.cs +++ b/IoTSharp.Hub/Startup.cs @@ -1,4 +1,6 @@ using IoTSharp.Hub.Data; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -8,10 +10,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; using NSwag.AspNetCore; using System; +using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Reflection; +using System.Text; namespace IoTSharp.Hub { @@ -38,11 +43,19 @@ namespace IoTSharp.Hub services.AddIoTSharpHub(Configuration); - services.AddDefaultIdentity() - .AddEntityFrameworkStores(); - - services.AddAuthentication().AddJwtBearer(); + services.AddIdentity() + .AddRoles() + .AddRoleManager>() + .AddDefaultTokenProviders() + .AddEntityFrameworkStores(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims + services.ConfigureJwtAuthentication(Configuration["JwtIssuer"], Configuration["JwtAudience"], Configuration["JwtKey"],TimeSpan.FromDays( Convert.ToInt32(Configuration["JwtExpireDays"]))); + + services.AddAuthorization(options => + { + options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build(); + }); services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; @@ -56,7 +69,7 @@ namespace IoTSharp.Hub configure.Version = typeof(Startup).GetTypeInfo().Assembly.GetName().Version.ToString(); configure.Description = description?.Description; }); - + services.AddTransient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } @@ -84,7 +97,7 @@ namespace IoTSharp.Hub app.UseCookiePolicy(); app.UseAuthentication(); - + app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto