mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 21:03:17 -07:00
work around the user management
This commit is contained in:
parent
5b49d03f85
commit
5ec9123851
19 changed files with 328 additions and 68 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -237,3 +237,4 @@ _Pvt_Extensions
|
|||
*.ncrunchproject
|
||||
*.ncrunchsolution
|
||||
|
||||
*.txt
|
||||
|
|
|
@ -11,5 +11,6 @@ namespace Ombi.Core.IdentityResolver
|
|||
Task<UserDto> GetUser(string username);
|
||||
Task<IEnumerable<UserDto>> GetUsers();
|
||||
Task DeleteUser(UserDto user);
|
||||
Task<UserDto> UpdateUser(UserDto userDto);
|
||||
}
|
||||
}
|
|
@ -84,6 +84,12 @@ namespace Ombi.Core.IdentityResolver
|
|||
await UserRepository.DeleteUser(Mapper.Map<User>(user));
|
||||
}
|
||||
|
||||
public async Task<UserDto> UpdateUser(UserDto userDto)
|
||||
{
|
||||
var user = Mapper.Map<User>(userDto);
|
||||
return Mapper.Map<UserDto>(await UserRepository.UpdateUser(user));
|
||||
}
|
||||
|
||||
private UserHash HashPassword(string password)
|
||||
{
|
||||
// generate a 128-bit salt using a secure PRNG
|
||||
|
|
|
@ -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<string> Claims { get; set; }
|
||||
public List<ClaimCheckboxes> 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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Claim, ClaimCheckboxes>
|
||||
{
|
||||
|
||||
public ClaimCheckboxes Convert(Claim source, ClaimCheckboxes destination, ResolutionContext context)
|
||||
{
|
||||
return new ClaimCheckboxes
|
||||
{
|
||||
Enabled = true,
|
||||
Value = source.Value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,11 +16,13 @@ namespace Ombi.Mapping.Profiles
|
|||
CreateMap<User, UserDto>().ReverseMap();
|
||||
|
||||
|
||||
CreateMap<UserDto, UserViewModel>()
|
||||
.ForMember(dest => dest.Claims, opts => opts.MapFrom(src => src.Claims.Select(x => x.Value).ToList())); // Map the claims to a List<string>
|
||||
CreateMap<Claim, ClaimCheckboxes>().ConvertUsing<ClaimsConverter>();
|
||||
|
||||
CreateMap<string, Claim>()
|
||||
.ConstructUsing(str => new Claim(ClaimTypes.Role, str)); // This is used for the UserViewModel List<string> claims => UserDto List<claim>
|
||||
CreateMap<UserDto, UserViewModel>().ForMember(x => x.Password, opt => opt.Ignore());
|
||||
|
||||
CreateMap<ClaimCheckboxes, Claim>()
|
||||
.ConstructUsing(checkbox => checkbox.Enabled ? new Claim(ClaimTypes.Role, checkbox.Value) : null);
|
||||
// This is used for the UserViewModel List<string> claims => UserDto List<claim>
|
||||
CreateMap<UserViewModel, UserDto>();
|
||||
|
||||
CreateMap<string, DateTime>().ConvertUsing<StringToDateTimeConverter>();
|
||||
|
|
|
@ -10,5 +10,6 @@ namespace Ombi.Store.Repository
|
|||
Task<User> GetUser(string username);
|
||||
Task<IEnumerable<User>> GetUsers();
|
||||
Task DeleteUser(User user);
|
||||
Task<User> UpdateUser(User user);
|
||||
}
|
||||
}
|
|
@ -64,5 +64,12 @@ namespace Ombi.Store.Repository
|
|||
Db.Users.Remove(user);
|
||||
await Db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<User> UpdateUser(User user)
|
||||
{
|
||||
Db.Users.Update(user);
|
||||
await Db.SaveChangesAsync();
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
/// <remarks>We have [AllowAnonymous] since when going through the wizard we do not have a JWT Token yet</remarks>
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
|
@ -66,16 +69,47 @@ namespace Ombi.Controllers
|
|||
[HttpGet("Users")]
|
||||
public async Task<IEnumerable<UserViewModel>> GetAllUsers()
|
||||
{
|
||||
return Mapper.Map<IEnumerable<UserViewModel>>(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<IEnumerable<UserViewModel>>(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<UserViewModel> CreateUser([FromBody] UserViewModel user)
|
||||
{
|
||||
user.Id = null;
|
||||
var userResult = await IdentityManager.CreateUser(Mapper.Map<UserDto>(user));
|
||||
return Mapper.Map<UserViewModel>(userResult);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<UserViewModel> UpdateUser([FromBody] UserViewModel user)
|
||||
{
|
||||
var userResult = await IdentityManager.UpdateUser(Mapper.Map<UserDto>(user));
|
||||
return Mapper.Map<UserViewModel>(userResult);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<StatusCodeResult> DeleteUser([FromBody] UserViewModel user)
|
||||
{
|
||||
|
@ -83,5 +117,18 @@ namespace Ombi.Controllers
|
|||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("claims")]
|
||||
public IEnumerable<ClaimCheckboxes> 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});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace Ombi
|
|||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -720,3 +720,4 @@ body {
|
|||
.ui-growl-item{
|
||||
margin-top:35px $i;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
<ul class="nav navbar-nav">
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/requests']"><i class="fa fa-plus"></i> Requests</a></li>
|
||||
</ul>
|
||||
<ul *ngIf="isAdmin || isPowerUser" class="nav navbar-nav">
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/usermanagement']"><i class="fa fa-user"></i> User Management</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -6,7 +6,7 @@ enum envs {
|
|||
live = 2
|
||||
}
|
||||
|
||||
var envVar = "#{Environment}";
|
||||
var envVar = '0';
|
||||
var env = envs.local;
|
||||
if (envs[envVar]) {
|
||||
env = envs[envVar];
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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<ICheckbox[]> {
|
||||
return this.http.get(`${this.url}/Claims`).map(this.extractData);
|
||||
}
|
||||
|
||||
createUser(user: IUser): Observable<IUser> {
|
||||
return this.http.post(this.url, JSON.stringify(user), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
|
||||
updateUser(user: IUser): Observable<IUser> {
|
||||
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) {
|
||||
|
|
|
@ -15,12 +15,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button type="button" class="btn btn-success-outline" (click)="showCreateDialogue=true">Add User</button>
|
||||
<!-- Table -->
|
||||
<table class="table table-striped table-hover table-responsive table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a (click)="changeSort('username')">
|
||||
<a>
|
||||
Username
|
||||
<!--<span ng-show="sortType == 'username' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'username' && sortReverse" class="fa fa-caret-up"></span>-->
|
||||
|
@ -58,13 +61,13 @@
|
|||
{{u.emailAddress}}
|
||||
</td>
|
||||
<td>
|
||||
<span *ngFor="let claim of u.claims">{{claim}}</span>
|
||||
<div *ngFor="let claim of u.claims"><span *ngIf="claim.enabled">{{claim.value}}</span></div>
|
||||
|
||||
</td>
|
||||
<td ng-hide="hideColumns">
|
||||
<span ng-if="u.userType === 1">Local User</span>
|
||||
<span ng-if="u.userType === 2">Plex User</span>
|
||||
<span ng-if="u.userType === 3">Emby User</span>
|
||||
<span *ngIf="u.userType === 1">Local User</span>
|
||||
<span *ngIf="u.userType === 2">Plex User</span>
|
||||
<span *ngIf="u.userType === 3">Emby User</span>
|
||||
</td>
|
||||
<td>
|
||||
<a (click)="edit(u)" class="btn btn-sm btn-info-outline">Details/Edit</a>
|
||||
|
@ -72,3 +75,110 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<div class="modal fade in" *ngIf="showEditDialog" style="display: block;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" (click)="showEditDialog=false">×</button>
|
||||
<h4 class="modal-title">Editing User {{selectedUser?.username}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Username</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="selectedUser.username" [readonly]="true" class="form-control form-control-custom " id="username" name="username" value="{{selectedUser?.username}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="alias" class="control-label">Alias</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="selectedUser.alias" class="form-control form-control-custom " id="alias" name="alias" value="{{selectedUser?.alias}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alias" class="control-label">Email Address</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="selectedUser.emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress" value="{{selectedUser?.emailAddress}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let c of selectedUser.claims">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label for="claim{{c.value}}">{{c.value}}</label>
|
||||
<input type="checkbox" [(ngModel)]="c.enabled" [value]="c.value" [checked]="c.value" id="claim{{c.value}}" name="claim{{c.value}}" ng-checked="c.enabled">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger-outline" (click)="showEditDialog=false">Close</button>
|
||||
<button type="button" class="btn btn-primary-outline" (click)="updateUser()">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="lightbox">
|
||||
<div class="modal fade in " *ngIf="showCreateDialogue" style="display: block;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" (click)="showCreateDialogue=false">×</button>
|
||||
<h4 class="modal-title">Create User</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Username</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="createdUser.username" class="form-control form-control-custom " id="username" name="username" value="{{createdUser?.username}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="alias" class="control-label">Alias</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="createdUser.alias" class="form-control form-control-custom " id="alias" name="alias" value="{{createdUser?.alias}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="emailAddress" class="control-label">Email Address</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="createdUser.emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress" value="{{createdUser?.emailAddress}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
<div>
|
||||
<input type="password" [(ngModel)]="createdUser.password" class="form-control form-control-custom " id="password" name="password" value="{{createdUser?.password}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngFor="let c of availableClaims">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label for="create{{c.value}}">{{c.value}}</label>
|
||||
<input type="checkbox" [(ngModel)]="c.enabled" [value]="c.value" [checked]="c.value" id="create{{c.value}}" name="create{{c.value}}" ng-checked="c.enabled">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger-outline" (click)="showCreateDialogue=false">Close</button>
|
||||
<button type="button" class="btn btn-primary-outline" (click)="create()">Add User</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
||||
changeSort(username: string) {
|
||||
//??????
|
||||
|
||||
updateUser() {
|
||||
this.showEditDialog = false;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue