diff --git a/.gitignore b/.gitignore index caef0e940..f58686865 100644 --- a/.gitignore +++ b/.gitignore @@ -237,3 +237,4 @@ _Pvt_Extensions *.ncrunchproject *.ncrunchsolution +*.txt diff --git a/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs b/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs index fad0d21a7..96a327080 100644 --- a/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs +++ b/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs @@ -11,5 +11,6 @@ namespace Ombi.Core.IdentityResolver Task GetUser(string username); Task> GetUsers(); Task DeleteUser(UserDto user); + Task UpdateUser(UserDto userDto); } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs b/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs index a38a5413c..46197ab27 100644 --- a/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs +++ b/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs @@ -84,6 +84,12 @@ namespace Ombi.Core.IdentityResolver await UserRepository.DeleteUser(Mapper.Map(user)); } + public async Task UpdateUser(UserDto userDto) + { + var user = Mapper.Map(userDto); + return Mapper.Map(await UserRepository.UpdateUser(user)); + } + private UserHash HashPassword(string password) { // generate a 128-bit salt using a secure PRNG diff --git a/Ombi/Ombi.Core/Models/UI/UserViewModel.cs b/Ombi/Ombi.Core/Models/UI/UserViewModel.cs index 504b771b3..fdb695b8e 100644 --- a/Ombi/Ombi.Core/Models/UI/UserViewModel.cs +++ b/Ombi/Ombi.Core/Models/UI/UserViewModel.cs @@ -7,9 +7,15 @@ namespace Ombi.Core.Models.UI public string Id { get; set; } public string Username { get; set; } public string Alias { get; set; } - public List Claims { get; set; } + public List Claims { get; set; } public string EmailAddress { get; set; } public string Password { get; set; } public UserType UserType { get; set; } } + + public class ClaimCheckboxes + { + public string Value { get; set; } + public bool Enabled { get; set; } + } } diff --git a/Ombi/Ombi.Mapping/MappingConverters.cs b/Ombi/Ombi.Mapping/MappingConverters.cs index 842e1a95e..4d88c152c 100644 --- a/Ombi/Ombi.Mapping/MappingConverters.cs +++ b/Ombi/Ombi.Mapping/MappingConverters.cs @@ -1,5 +1,7 @@ using AutoMapper; using System; +using System.Security.Claims; +using Ombi.Core.Models.UI; namespace Ombi.Mapping { @@ -23,4 +25,17 @@ namespace Ombi.Mapping return default(DateTime); } } + + public class ClaimsConverter : ITypeConverter + { + + public ClaimCheckboxes Convert(Claim source, ClaimCheckboxes destination, ResolutionContext context) + { + return new ClaimCheckboxes + { + Enabled = true, + Value = source.Value + }; + } + } } \ No newline at end of file diff --git a/Ombi/Ombi.Mapping/Profiles/OmbiProfile.cs b/Ombi/Ombi.Mapping/Profiles/OmbiProfile.cs index 4b029a9e5..c64e61587 100644 --- a/Ombi/Ombi.Mapping/Profiles/OmbiProfile.cs +++ b/Ombi/Ombi.Mapping/Profiles/OmbiProfile.cs @@ -16,11 +16,13 @@ namespace Ombi.Mapping.Profiles CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.Claims, opts => opts.MapFrom(src => src.Claims.Select(x => x.Value).ToList())); // Map the claims to a List + CreateMap().ConvertUsing(); - CreateMap() - .ConstructUsing(str => new Claim(ClaimTypes.Role, str)); // This is used for the UserViewModel List claims => UserDto List + CreateMap().ForMember(x => x.Password, opt => opt.Ignore()); + + CreateMap() + .ConstructUsing(checkbox => checkbox.Enabled ? new Claim(ClaimTypes.Role, checkbox.Value) : null); + // This is used for the UserViewModel List claims => UserDto List CreateMap(); CreateMap().ConvertUsing(); diff --git a/Ombi/Ombi.Store/Repository/IUserRepository.cs b/Ombi/Ombi.Store/Repository/IUserRepository.cs index be5d28af6..8a1ee1c47 100644 --- a/Ombi/Ombi.Store/Repository/IUserRepository.cs +++ b/Ombi/Ombi.Store/Repository/IUserRepository.cs @@ -10,5 +10,6 @@ namespace Ombi.Store.Repository Task GetUser(string username); Task> GetUsers(); Task DeleteUser(User user); + Task UpdateUser(User user); } } \ No newline at end of file diff --git a/Ombi/Ombi.Store/Repository/UserRepository.cs b/Ombi/Ombi.Store/Repository/UserRepository.cs index 9dec31470..518441a0f 100644 --- a/Ombi/Ombi.Store/Repository/UserRepository.cs +++ b/Ombi/Ombi.Store/Repository/UserRepository.cs @@ -64,5 +64,12 @@ namespace Ombi.Store.Repository Db.Users.Remove(user); await Db.SaveChangesAsync(); } + + public async Task UpdateUser(User user) + { + Db.Users.Update(user); + await Db.SaveChangesAsync(); + return user; + } } } \ No newline at end of file diff --git a/Ombi/Ombi/Auth/TokenProviderMiddleware.cs b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs index 5fcf82375..0681f503b 100644 --- a/Ombi/Ombi/Auth/TokenProviderMiddleware.cs +++ b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs @@ -60,7 +60,7 @@ namespace Ombi.Auth private async Task GenerateToken(HttpContext context) { var request = context.Request; - UserAuthModel userInfo; // TODO use a stong type + UserAuthModel userInfo; using (var bodyReader = new StreamReader(request.Body)) { diff --git a/Ombi/Ombi/Controllers/IdentityController.cs b/Ombi/Ombi/Controllers/IdentityController.cs index a0802886b..2d3a2e640 100644 --- a/Ombi/Ombi/Controllers/IdentityController.cs +++ b/Ombi/Ombi/Controllers/IdentityController.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Security.Claims; using System.Threading.Tasks; using AutoMapper; @@ -38,6 +40,7 @@ namespace Ombi.Controllers /// This should never be called after this. /// The reason why we return false if users exists is that this method doesn't have any /// authorization and could be called from anywhere. + /// We have [AllowAnonymous] since when going through the wizard we do not have a JWT Token yet /// /// /// @@ -66,15 +69,46 @@ namespace Ombi.Controllers [HttpGet("Users")] public async Task> GetAllUsers() { - return Mapper.Map>(await IdentityManager.GetUsers()); + var type = typeof(OmbiClaims); + FieldInfo[] fieldInfos = type.GetFields(BindingFlags.Public | + BindingFlags.Static | BindingFlags.FlattenHierarchy); + + var fields = fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly).ToList(); + var allClaims = fields.Select(x => x.Name).ToList(); + var users = Mapper.Map>(await IdentityManager.GetUsers()).ToList(); + + foreach (var user in users) + { + var userClaims = user.Claims.Select(x => x.Value); + var left = allClaims.Except(userClaims); + + foreach (var c in left) + { + user.Claims.Add(new ClaimCheckboxes + { + Enabled = false, + Value = c + }); + } + } + + return users; } [HttpPost] public async Task CreateUser([FromBody] UserViewModel user) { + user.Id = null; var userResult = await IdentityManager.CreateUser(Mapper.Map(user)); return Mapper.Map(userResult); } + + [HttpPut] + public async Task UpdateUser([FromBody] UserViewModel user) + { + var userResult = await IdentityManager.UpdateUser(Mapper.Map(user)); + return Mapper.Map(userResult); + } [HttpDelete] public async Task DeleteUser([FromBody] UserViewModel user) @@ -82,6 +116,19 @@ namespace Ombi.Controllers await IdentityManager.DeleteUser(Mapper.Map(user)); return Ok(); } + + [HttpGet("claims")] + public IEnumerable GetAllClaims() + { + var type = typeof(OmbiClaims); + FieldInfo[] fieldInfos = type.GetFields(BindingFlags.Public | + BindingFlags.Static | BindingFlags.FlattenHierarchy); + + var fields = fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly).ToList(); + var allClaims = fields.Select(x => x.Name).ToList(); + + return allClaims.Select(x => new ClaimCheckboxes() {Value = x}); + } } } diff --git a/Ombi/Ombi/Program.cs b/Ombi/Ombi/Program.cs index 1be157110..ee9a414ee 100644 --- a/Ombi/Ombi/Program.cs +++ b/Ombi/Ombi/Program.cs @@ -20,6 +20,7 @@ namespace Ombi .UseStartup() .Build(); + host.Run(); } } diff --git a/Ombi/Ombi/Styles/base.scss b/Ombi/Ombi/Styles/base.scss index f8ca8f348..b41fa81a0 100644 --- a/Ombi/Ombi/Styles/base.scss +++ b/Ombi/Ombi/Styles/base.scss @@ -719,4 +719,5 @@ body { .ui-growl-item{ margin-top:35px $i; -} \ No newline at end of file +} + diff --git a/Ombi/Ombi/wwwroot/app/app.component.html b/Ombi/Ombi/wwwroot/app/app.component.html index c6be459a7..442f78863 100644 --- a/Ombi/Ombi/wwwroot/app/app.component.html +++ b/Ombi/Ombi/wwwroot/app/app.component.html @@ -22,7 +22,10 @@
  • Search
  • + diff --git a/Ombi/Ombi/wwwroot/app/app.module.ts b/Ombi/Ombi/wwwroot/app/app.module.ts index c17b810ed..10ecb633f 100644 --- a/Ombi/Ombi/wwwroot/app/app.module.ts +++ b/Ombi/Ombi/wwwroot/app/app.module.ts @@ -36,7 +36,7 @@ import { StatusService } from './services/status.service'; import { SettingsModule } from './settings/settings.module'; import { WizardModule } from './wizard/wizard.module'; -import { ButtonModule } from 'primeng/primeng'; +import { ButtonModule, DialogModule } from 'primeng/primeng'; import { GrowlModule } from 'primeng/components/growl/growl'; import { DataTableModule, SharedModule } from 'primeng/primeng'; @@ -64,7 +64,8 @@ const routes: Routes = [ SharedModule, InfiniteScrollModule, AuthModule, - WizardModule + WizardModule, + DialogModule ], declarations: [ AppComponent, diff --git a/Ombi/Ombi/wwwroot/app/config.ts b/Ombi/Ombi/wwwroot/app/config.ts index 0f365a170..add945489 100644 --- a/Ombi/Ombi/wwwroot/app/config.ts +++ b/Ombi/Ombi/wwwroot/app/config.ts @@ -6,7 +6,7 @@ enum envs { live = 2 } -var envVar = "#{Environment}"; +var envVar = '0'; var env = envs.local; if (envs[envVar]) { env = envs[envVar]; diff --git a/Ombi/Ombi/wwwroot/app/interfaces/IUser.ts b/Ombi/Ombi/wwwroot/app/interfaces/IUser.ts index c1e9a62ca..109fed70c 100644 --- a/Ombi/Ombi/wwwroot/app/interfaces/IUser.ts +++ b/Ombi/Ombi/wwwroot/app/interfaces/IUser.ts @@ -2,7 +2,7 @@ id: string, username: string, alias: string, - claims: string[], + claims: ICheckbox[], emailAddress: string, password: string, userType : UserType, @@ -14,3 +14,9 @@ export enum UserType { PlexUser = 2, EmbyUser = 3 } + + +export interface ICheckbox { + value: string, + enabled:boolean, +} \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/services/identity.service.ts b/Ombi/Ombi/wwwroot/app/services/identity.service.ts index cd32dbf49..69e81c7a0 100644 --- a/Ombi/Ombi/wwwroot/app/services/identity.service.ts +++ b/Ombi/Ombi/wwwroot/app/services/identity.service.ts @@ -4,7 +4,7 @@ import { Http } from '@angular/http'; import { Observable } from 'rxjs/Rx'; import { ServiceAuthHelpers } from './service.helpers'; -import { IUser } from '../interfaces/IUser'; +import { IUser, ICheckbox } from '../interfaces/IUser'; @Injectable() @@ -24,6 +24,18 @@ export class IdentityService extends ServiceAuthHelpers { return this.http.get(`${this.url}/Users`).map(this.extractData); } + getAllAvailableClaims(): Observable { + return this.http.get(`${this.url}/Claims`).map(this.extractData); + } + + createUser(user: IUser): Observable { + return this.http.post(this.url, JSON.stringify(user), { headers: this.headers }).map(this.extractData); + } + + updateUser(user: IUser): Observable { + return this.http.put(this.url, JSON.stringify(user), { headers: this.headers }).map(this.extractData); + } + hasRole(role: string): boolean { var roles = localStorage.getItem("roles") as string[]; if (roles) { diff --git a/Ombi/Ombi/wwwroot/app/usermanagement/usermanagement.component.html b/Ombi/Ombi/wwwroot/app/usermanagement/usermanagement.component.html index c0f4bfe71..fb0392866 100644 --- a/Ombi/Ombi/wwwroot/app/usermanagement/usermanagement.component.html +++ b/Ombi/Ombi/wwwroot/app/usermanagement/usermanagement.component.html @@ -15,60 +15,170 @@ + + + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + -
    - - Username - - - - - Alias - - - - Email - - - Roles - - - User Type - -
    + + Username + + + + + Alias + + + + Email + + + Roles + + + User Type + +
    - {{u.username}} - - {{u.alias}} - - {{u.emailAddress}} - - {{claim}} - - - Local User - Plex User - Emby User - - Details/Edit -
    + {{u.username}} + + {{u.alias}} + + {{u.emailAddress}} + +
    {{claim.value}}
    + +
    + Local User + Plex User + Emby User + + Details/Edit +
    \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/usermanagement/usermanagement.component.ts b/Ombi/Ombi/wwwroot/app/usermanagement/usermanagement.component.ts index 89c08b01c..024261b2e 100644 --- a/Ombi/Ombi/wwwroot/app/usermanagement/usermanagement.component.ts +++ b/Ombi/Ombi/wwwroot/app/usermanagement/usermanagement.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { IUser } from '../interfaces/IUser'; +import { IUser, ICheckbox } from '../interfaces/IUser'; import { IdentityService } from '../services/identity.service'; @Component({ @@ -16,17 +16,57 @@ export class UserManagementComponent implements OnInit { this.identityService.getUsers().subscribe(x => { this.users = x; }); + this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); + + this.resetCreatedUser(); } users: IUser[]; selectedUser: IUser; + createdUser: IUser; + availableClaims : ICheckbox[]; + showEditDialog = false; + showCreateDialogue = false; + edit(user: IUser) { this.selectedUser = user; + this.showEditDialog = true; + } + + updateUser() { + this.showEditDialog = false; } - changeSort(username: string) { - //?????? + + create() { + this.createdUser.claims = this.availableClaims; + this.identityService.createUser(this.createdUser).subscribe(x => { + this.users.push(x); // Add the new user + + this.showCreateDialogue = false; + this.resetCreatedUser(); + }); + + } + + private resetClaims() { + //this.availableClaims.forEach(x => { + // x.enabled = false; + //}); + } + + private resetCreatedUser() { + this.createdUser = { + id: "-1", + alias: "", + claims: [], + emailAddress: "", + password: "", + userType: 1, + username: "" + } + this.resetClaims(); } //private removeRequestFromUi(key : IRequestModel) {