Finished implimenting Identity with IdentityServer4. #865 #1456

pull/1488/head
Jamie.Rees 7 years ago
parent b04344dd17
commit 51fbd56c44

@ -22,7 +22,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Fact]
public async Task Should_ReturnSuccess_WhenAdminAndRequestMovie()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.Admin)).Returns(true);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -33,7 +33,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Fact]
public async Task Should_ReturnSuccess_WhenAdminAndRequestTV()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.Admin)).Returns(true);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -44,7 +44,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Fact]
public async Task Should_ReturnSuccess_WhenAutoApproveMovieAndRequestMovie()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.AutoApproveMovie)).Returns(true);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.AutoApproveMovie)).Returns(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -55,7 +55,7 @@ namespace Ombi.Core.Tests.Rule.Request
[Fact]
public async Task Should_ReturnSuccess_WhenAutoApproveTVAndRequestTV()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.AutoApproveTv)).Returns(true);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.AutoApproveTv)).Returns(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);

@ -23,7 +23,7 @@ namespace Ombi.Core.Tests.Rule
[Fact]
public async Task Should_ReturnSuccess_WhenRequestingMovieWithMovieRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.RequestMovie)).Returns(true);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestMovie)).Returns(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -33,7 +33,7 @@ namespace Ombi.Core.Tests.Rule
[Fact]
public async Task Should_ReturnFail_WhenRequestingMovieWithoutMovieRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.RequestMovie)).Returns(false);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestMovie)).Returns(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -44,7 +44,7 @@ namespace Ombi.Core.Tests.Rule
[Fact]
public async Task Should_ReturnSuccess_WhenRequestingMovieWithAdminRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.Admin)).Returns(true);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie };
var result = await Rule.Execute(request);
@ -54,7 +54,7 @@ namespace Ombi.Core.Tests.Rule
[Fact]
public async Task Should_ReturnSuccess_WhenRequestingTVWithAdminRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.Admin)).Returns(true);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.Admin)).Returns(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -64,7 +64,7 @@ namespace Ombi.Core.Tests.Rule
[Fact]
public async Task Should_ReturnSuccess_WhenRequestingTVWithTVRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.RequestTv)).Returns(true);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestTv)).Returns(true);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);
@ -74,7 +74,7 @@ namespace Ombi.Core.Tests.Rule
[Fact]
public async Task Should_ReturnFail_WhenRequestingTVWithoutTVRole()
{
PrincipalMock.Setup(x => x.IsInRole(OmbiClaims.RequestTv)).Returns(false);
PrincipalMock.Setup(x => x.IsInRole(OmbiRoles.RequestTv)).Returns(false);
var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow };
var result = await Rule.Execute(request);

@ -1,6 +1,6 @@
namespace Ombi.Core.Claims
{
public static class OmbiClaims
public static class OmbiRoles
{
public const string Admin = nameof(Admin);
public const string AutoApproveMovie = nameof(AutoApproveMovie);

@ -0,0 +1,62 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;
namespace Ombi.Core.Helpers
{
public static class EmailValidator
{
static bool _invalid;
public static bool IsValidEmail(string strIn)
{
_invalid = false;
if (string.IsNullOrEmpty(strIn))
return false;
// Use IdnMapping class to convert Unicode domain names.
try
{
strIn = Regex.Replace(strIn, "(@)(.+)$", DomainMapper,
RegexOptions.None, TimeSpan.FromMilliseconds(200));
}
catch (RegexMatchTimeoutException)
{
return false;
}
if (_invalid)
return false;
// Return true if strIn is in valid e-mail format.
try
{
return Regex.IsMatch(strIn,
@"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$",
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
}
catch (RegexMatchTimeoutException)
{
return false;
}
}
private static string DomainMapper(Match match)
{
// IdnMapping class with default property values.
IdnMapping idn = new IdnMapping();
string domainName = match.Groups[2].Value;
try
{
domainName = idn.GetAscii(domainName);
}
catch (ArgumentException)
{
_invalid = true;
}
return match.Groups[1].Value + domainName;
}
}
}

@ -19,15 +19,15 @@ namespace Ombi.Core.Rule.Rules.Request
public Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiClaims.Admin))
if (User.IsInRole(OmbiRoles.Admin))
{
obj.Approved = true;
return Task.FromResult(Success());
}
if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiClaims.AutoApproveMovie))
if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiRoles.AutoApproveMovie))
obj.Approved = true;
if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiClaims.AutoApproveTv))
if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv))
obj.Approved = true;
return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve
}

@ -18,17 +18,17 @@ namespace Ombi.Core.Rule.Rules
public Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiClaims.Admin))
if (User.IsInRole(OmbiRoles.Admin))
return Task.FromResult(Success());
if (obj.RequestType == RequestType.Movie)
{
if (User.IsInRole(OmbiClaims.RequestMovie))
if (User.IsInRole(OmbiRoles.RequestMovie))
return Task.FromResult(Success());
return Task.FromResult(Fail("You do not have permissions to Request a Movie"));
}
if (User.IsInRole(OmbiClaims.RequestTv))
if (User.IsInRole(OmbiRoles.RequestTv))
return Task.FromResult(Success());
return Task.FromResult(Fail("You do not have permissions to Request a Movie"));
}

@ -21,7 +21,7 @@ namespace Ombi.Core.Rule.Rules.Specific
var req = (BaseRequest)obj;
var sendNotification = !req.Approved; /*|| !prSettings.IgnoreNotifyForAutoApprovedRequests;*/
if (User.IsInRole(OmbiClaims.Admin))
if (User.IsInRole(OmbiRoles.Admin))
sendNotification = false; // Don't bother sending a notification if the user is an admin
return Task.FromResult(new RuleResult
{

@ -36,7 +36,11 @@ namespace Ombi.Store.Context
public DbSet<MovieIssues> MovieIssues { get; set; }
public DbSet<TvIssues> TvIssues { get; set; }
public DbSet<EmailTokens> EmailTokens { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{

@ -9,8 +9,8 @@ namespace Ombi.Attributes
{
var roles = new []
{
OmbiClaims.Admin,
OmbiClaims.PowerUser
OmbiRoles.Admin,
OmbiRoles.PowerUser
};
Roles = string.Join(",",roles);
}

@ -34,7 +34,7 @@
<li [routerLinkActive]="['active']" class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> Welcome {{user.name}} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/user/changepassword']"><i class="fa fa-key"></i> Change Password</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/usermanagement/updatedetails']"><i class="fa fa-key"></i> Update Details</a></li>
<li [routerLinkActive]="['active']"><a (click)="logOut()"><i class="fa fa-sign-out"></i> Logout</a></li>
</ul>
</li>

@ -44,8 +44,8 @@ export class AuthService extends ServiceHelpers {
throw "Invalid token";
}
var json = this.jwtHelper.decodeToken(token);
var roles = json["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]
var name = json["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"];
var roles = json["role"];
var name = json["name"];
var u = { name: name, roles: [] as string[] };
@ -64,7 +64,6 @@ export class AuthService extends ServiceHelpers {
logout() {
localStorage.removeItem('id_token');
localStorage.removeItem('currentUser');
}
}

@ -20,3 +20,13 @@ export interface ICheckbox {
enabled: boolean,
}
export interface IIdentityResult {
errors: string[],
successful: boolean,
}
export interface IUpdateLocalUser extends IUser {
currentPassword: string,
confirmNewPassword: string
}

@ -4,7 +4,7 @@ import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { ServiceAuthHelpers } from './service.helpers';
import { IUser, ICheckbox } from '../interfaces/IUser';
import { IUser, IUpdateLocalUser, ICheckbox, IIdentityResult } from '../interfaces/IUser';
@Injectable()
@ -20,7 +20,7 @@ export class IdentityService extends ServiceAuthHelpers {
return this.http.get(this.url).map(this.extractData);
}
getUserById(id: number): Observable<IUser> {
getUserById(id: string): Observable<IUser> {
return this.http.get(`${this.url}User/${id}`).map(this.extractData);
}
@ -32,12 +32,19 @@ export class IdentityService extends ServiceAuthHelpers {
return this.http.get(`${this.url}Claims`).map(this.extractData);
}
createUser(user: IUser): Observable<IUser> {
createUser(user: IUser): Observable<IIdentityResult> {
return this.http.post(this.url, JSON.stringify(user), { headers: this.headers }).map(this.extractData);
}
updateUser(user: IUser): Observable<IUser> {
updateUser(user: IUser): Observable<IIdentityResult> {
return this.http.put(this.url, JSON.stringify(user), { headers: this.headers }).map(this.extractData);
}
updateLocalUser(user: IUpdateLocalUser): Observable<IIdentityResult> {
return this.http.put(this.url + 'local', JSON.stringify(user), { headers: this.headers }).map(this.extractData);
}
deleteUser(user: IUser): Observable<IIdentityResult> {
return this.http.delete(`${this.url}/${user.id}`, { headers: this.headers }).map(this.extractData);
}
hasRole(role: string): boolean {

@ -22,7 +22,7 @@
<div *ngIf="emailForm.get('host').hasError('required')">Host is required</div>
<div *ngIf="emailForm.get('port').hasError('required')">The Port is required</div>
<div *ngIf="emailForm.get('sender').hasError('required')">The Email Sender is required</div>
<div *ngIf="emailForm.get('sender').hasError('email')">The Email Sender needs to be a valid email address</div>
<div *ngIf="emailForm.get('sender').hasError('incorrectMailFormat')">The Email Sender needs to be a valid email address</div>
<div *ngIf="emailForm.get('adminEmail').hasError('required')">The Email Sender is required</div>
<div *ngIf="emailForm.get('adminEmail').hasError('email')">The Admin Email needs to be a valid email address</div>
<div *ngIf="emailForm.get('username').hasError('required')">The Username is required</div>

@ -0,0 +1,47 @@
<div *ngIf="form">
<h3>Hello {{form.value.username}}!</h3>
<div class="col-md-6">
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
<div class="modal-body" style="margin-top:45px;">
<div class="form-group">
<label for="emailAddress" class="control-label">Email Address</label>
<div>
<input type="text" formControlName="emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress">
</div>
</div>
<div class="form-group">
<label for="currentPassword" class="control-label">Current Password</label>
<div>
<input type="password" formControlName="currentPassword" class="form-control form-control-custom " id="currentPassword" name="currentPassword">
</div>
</div>
<div class="form-group">
<label for="password" class="control-label">New Password</label>
<div>
<input type="password" formControlName="password" class="form-control form-control-custom " id="password" name="password">
</div>
</div>
<div class="form-group">
<label for="confirmPassword" class="control-label">Confirm New Password</label>
<div>
<input type="password" formControlName="confirmNewPassword" class="form-control form-control-custom " id="confirmPassword"
name="confirmPassword">
</div>
</div>
</div>
<div>
<button type="submit" class="btn btn-primary-outline" [disabled]="form.invalid">Save</button>
</div>
</form>
</div>
<div class="col-md-6">
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
<div *ngIf="form.get('emailAddress').hasError('email')">Email address format is incorrect</div>
<div *ngIf="form.get('password').hasError('required')">The Password is required</div>
<div *ngIf="form.get('confirmNewPassword').hasError('required')">The Confirm New Password is required</div>
<div *ngIf="form.get('currentPassword').hasError('required')">Your current passowrd is required</div>
</div>
</div>
</div>

@ -0,0 +1,61 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { IUpdateLocalUser } from '../interfaces/IUser';
import { IdentityService } from '../services/identity.service';
import { NotificationService } from '../services/notification.service';
@Component({
templateUrl: './updatedetails.component.html'
})
export class UpdateDetailsComponent implements OnInit {
constructor(private identityService: IdentityService,
private notificationService: NotificationService,
private fb: FormBuilder) { }
form: FormGroup;
ngOnInit(): void {
this.identityService.getUser().subscribe(x => {
var localUser = x as IUpdateLocalUser;
this.form = this.fb.group({
id:[localUser.id],
username: [localUser.username],
emailAddress: [localUser.emailAddress, [Validators.email]],
confirmNewPassword: [localUser.confirmNewPassword],
currentPassword: [localUser.currentPassword, [Validators.required]],
password: [localUser.password],
});
});
}
onSubmit(form : FormGroup) {
if (form.invalid) {
this.notificationService.error("Validation", "Please check your entered values");
return
}
if (form.controls["password"].dirty) {
if (form.value.password !== form.value.confirmNewPassword) {
this.notificationService.error("Error", "Passwords do not match");
return;
}
}
this.identityService.updateLocalUser(this.form.value).subscribe(x => {
if (x.successful) {
this.notificationService.success("Updated", `All of your details have now been updated`)
} else {
x.errors.forEach((val) => {
this.notificationService.error("Error", val);
});
}
});
}
}

@ -18,10 +18,24 @@
</div>
<div class="form-group">
<label for="alias" class="control-label">Email Address</label>
<label for="emailAddress" class="control-label">Email Address</label>
<div>
<input type="text" [(ngModel)]="user.emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress" value="{{user?.emailAddress}}">
</div>
</div>
<div class="form-group">
<label for="password" class="control-label">Password</label>
<div>
<input type="password" [(ngModel)]="user.password" class="form-control form-control-custom " id="password" name="password">
</div>
</div>
<div class="form-group">
<label for="confirmPass" class="control-label">Confirm Password</label>
<div>
<input type="password" [(ngModel)]="confirmPass" class="form-control form-control-custom " id="confirmPass" name="confirmPass">
</div>
</div>
<div *ngFor="let c of availableClaims">
<div class="form-group">
@ -31,12 +45,9 @@
</div>
</div>
</div>
</div>
<div>
<button type="button" class="btn btn-danger-outline" (click)="update()">Create</button>
<button type="button" class="btn btn-danger-outline" (click)="create()">Create</button>
</div>

@ -12,10 +12,11 @@ import { NotificationService } from '../services/notification.service';
export class UserManagementAddComponent implements OnInit {
constructor(private identityService: IdentityService,
private notificationSerivce: NotificationService,
private router : Router) { }
private router: Router) { }
user: IUser;
availableClaims: ICheckbox[];
confirmPass: "";
ngOnInit(): void {
this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x);
@ -30,11 +31,35 @@ export class UserManagementAddComponent implements OnInit {
}
}
update(): void {
create(): void {
this.user.claims = this.availableClaims;
if (this.user.password) {
if (this.user.password !== this.confirmPass) {
this.notificationSerivce.error("Error", "Passwords do not match");
return;
}
}
var hasClaims = this.availableClaims.some((item) => {
if (item.enabled) { return true; }
return false;
});
if (!hasClaims) {
this.notificationSerivce.error("Error", "Please assign a role");
return;
}
this.identityService.createUser(this.user).subscribe(x => {
this.notificationSerivce.success("Updated", `The user ${this.user.username} has been created successfully`)
this.router.navigate(['usermanagement']);
if (x.successful) {
this.notificationSerivce.success("Updated", `The user ${this.user.username} has been created successfully`)
this.router.navigate(['usermanagement']);
} else {
x.errors.forEach((val) => {
this.notificationSerivce.error("Error", val);
});
}
})
}

@ -37,7 +37,8 @@
</div>
</div>
<div>
<button type="button" class="btn btn-danger-outline" (click)="update()">Update</button>
<button type="button" class="btn btn-primary-outline" (click)="update()">Update</button>
<button type="button" class="btn btn-danger-outline" (click)="delete()">Delete</button>
</div>
</div>

@ -1,4 +1,5 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { IUser } from '../interfaces/IUser';
import { IdentityService } from '../services/identity.service';
@ -11,10 +12,11 @@ import { ActivatedRoute } from '@angular/router';
export class UserManagementEditComponent {
constructor(private identityService: IdentityService,
private route: ActivatedRoute,
private notificationSerivce: NotificationService) {
private notificationSerivce: NotificationService,
private router: Router) {
this.route.params
.subscribe(params => {
this.userId = +params['id']; // (+) converts string 'id' to a number
this.userId = params['id'];
this.identityService.getUserById(this.userId).subscribe(x => {
this.user = x;
@ -23,13 +25,44 @@ export class UserManagementEditComponent {
}
user: IUser;
userId: number;
userId: string;
delete(): void {
this.identityService.deleteUser(this.user).subscribe(x => {
if (x.successful) {
this.notificationSerivce.success("Deleted", `The user ${this.user.username} was deleted`)
this.router.navigate(['usermanagement']);
} else {
x.errors.forEach((val) => {
this.notificationSerivce.error("Error", val);
});
}
});
}
update(): void {
this.identityService.updateUser(this.user).subscribe(x => {
var hasClaims = this.user.claims.some((item) => {
if (item.enabled) { return true; }
this.notificationSerivce.success("Updated",`The user ${this.user.username} has been updated successfully`)
return false;
});
if (!hasClaims) {
this.notificationSerivce.error("Error", "Please assign a role");
return;
}
this.identityService.updateUser(this.user).subscribe(x => {
if (x.successful) {
this.notificationSerivce.success("Updated", `The user ${this.user.username} has been updated successfully`)
this.router.navigate(['usermanagement']);
} else {
x.errors.forEach((val) => {
this.notificationSerivce.error("Error", val);
});
}
})
}
}

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { IUser, ICheckbox } from '../interfaces/IUser';
import { IUser } from '../interfaces/IUser';
import { IdentityService } from '../services/identity.service';
@Component({
@ -15,65 +15,10 @@ 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;
this.identityService.updateUser(this.selectedUser).subscribe(x => this.selectedUser = x);
}
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) {
// var index = this.requests.indexOf(key, 0);
// if (index > -1) {
// this.requests.splice(index, 1);
// }
//}
}

@ -1,13 +1,14 @@
import { NgModule, } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { UserManagementComponent } from './usermanagement.component';
import { UserManagementEditComponent } from './usermanagement-edit.component';
import { UserManagementAddComponent } from './usermanagement-add.component';
import { UpdateDetailsComponent } from './updatedetails.component';
import { IdentityService } from '../services/identity.service';
@ -17,19 +18,22 @@ const routes: Routes = [
{ path: 'usermanagement', component: UserManagementComponent, canActivate: [AuthGuard] },
{ path: 'usermanagement/add', component: UserManagementAddComponent, canActivate: [AuthGuard] },
{ path: 'usermanagement/edit/:id', component: UserManagementEditComponent, canActivate: [AuthGuard] },
{ path: 'usermanagement/updatedetails', component: UpdateDetailsComponent, canActivate: [AuthGuard] },
];
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
RouterModule.forChild(routes),
NgbModule.forRoot(),
],
declarations: [
UserManagementComponent,
UserManagementAddComponent,
UserManagementEditComponent
UserManagementEditComponent,
UpdateDetailsComponent
],
exports: [
RouterModule

@ -1,18 +1,23 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Ombi.Attributes;
using Ombi.Core.Claims;
using Ombi.Core.Helpers;
using Ombi.Core.Models.UI;
using Ombi.Models;
using Ombi.Models.Identity;
using Ombi.Store.Entities;
using IdentityResult = Ombi.Models.Identity.IdentityResult;
namespace Ombi.Controllers
{
@ -34,16 +39,6 @@ namespace Ombi.Controllers
private RoleManager<IdentityRole> RoleManager { get; }
private IMapper Mapper { get; }
/// <summary>
/// Gets the current user.
/// </summary>
/// <returns>Information about the current user</returns>
[HttpGet]
public async Task<UserViewModel> GetUser()
{
return Mapper.Map<UserViewModel>(await UserManager.GetUserAsync(User));
}
/// <summary>
/// This is what the Wizard will call when creating the user for the very first time.
/// This should never be called after this.
@ -68,29 +63,18 @@ namespace Ombi.Controllers
var userToCreate = new OmbiUser
{
UserName = user.Username,
UserType = UserType.LocalUser
};
var result = await UserManager.CreateAsync(userToCreate, user.Password);
if (result.Succeeded)
{
if (!(await RoleManager.RoleExistsAsync("Admin")))
if (!await RoleManager.RoleExistsAsync(OmbiRoles.Admin))
{
var r = await RoleManager.CreateAsync(new IdentityRole("Admin"));
await RoleManager.CreateAsync(new IdentityRole(OmbiRoles.Admin));
}
var re = await UserManager.AddToRoleAsync(userToCreate, "Admin");
var v = User.IsInRole("Admin");
await UserManager.AddToRoleAsync(userToCreate, OmbiRoles.Admin);
}
//await UserManager.CreateUser(new UserDto
//{
// Username = user.Username,
// UserType = UserType.LocalUser,
// Claims = new List<Claim>() { new Claim(ClaimTypes.Role, OmbiClaims.Admin) },
// Password = user.Password,
//});
return true;
}
@ -102,30 +86,30 @@ namespace Ombi.Controllers
[HttpGet("Users")]
public async Task<IEnumerable<UserViewModel>> GetAllUsers()
{
var type = typeof(OmbiClaims);
FieldInfo[] fieldInfos = type.GetFields(BindingFlags.Public |
BindingFlags.Static | BindingFlags.FlattenHierarchy);
var users = await UserManager.Users
.ToListAsync();
var fields = fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly).ToList();
var allClaims = fields.Select(x => x.Name).ToList();
var users = Mapper.Map<IEnumerable<UserViewModel>>(UserManager.Users).ToList();
var model = new List<UserViewModel>();
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
});
}
model.Add(await GetUserWithRoles(user));
}
return users;
return model;
}
/// <summary>
/// Gets the current logged in user.
/// </summary>
/// <returns>Information about all users</returns>
[HttpGet]
[Authorize]
public async Task<UserViewModel> GetCurrentUser()
{
var user = await UserManager.GetUserAsync(User);
return await GetUserWithRoles(user);
}
/// <summary>
@ -133,86 +117,299 @@ namespace Ombi.Controllers
/// </summary>
/// <returns>Information about the user</returns>
[HttpGet("User/{id}")]
public async Task<UserViewModel> GetUser(int id)
public async Task<UserViewModel> GetUser(string id)
{
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 user = Mapper.Map<UserViewModel>(await UserManager.Users.FirstOrDefaultAsync(x => x.Id == id.ToString()));
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == id);
return await GetUserWithRoles(user);
}
var userClaims = user.Claims.Select(x => x.Value);
IEnumerable<string> left = allClaims.Except(userClaims);
private async Task<UserViewModel> GetUserWithRoles(OmbiUser user)
{
var userRoles = await UserManager.GetRolesAsync(user);
var vm = new UserViewModel
{
Alias = user.Alias,
Username = user.UserName,
Id = user.Id,
EmailAddress = user.Email,
UserType = (Core.Models.UserType)(int)user.UserType,
Claims = new List<ClaimCheckboxes>(),
};
foreach (var c in left)
foreach (var role in userRoles)
{
user.Claims.Add(new ClaimCheckboxes
vm.Claims.Add(new ClaimCheckboxes
{
Enabled = false,
Value = c
Value = role,
Enabled = true
});
}
// Add the missing claims
var allRoles = await RoleManager.Roles.ToListAsync();
var missing = allRoles.Select(x => x.Name).Except(userRoles);
foreach (var role in missing)
{
vm.Claims.Add(new ClaimCheckboxes
{
Value = role,
Enabled = false
});
}
return user;
return vm;
}
/// <summary>
/// Creates the user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name = "user" > The user.</param>
/// <returns></returns>
//[HttpPost]
//public async Task<UserViewModel> CreateUser([FromBody] UserViewModel user)
//{
// user.Id = null;
// var userResult = await UserManager.CreateUser(Mapper.Map<UserDto>(user));
// return Mapper.Map<UserViewModel>(userResult);
//}
[HttpPost]
public async Task<IdentityResult> CreateUser([FromBody] UserViewModel user)
{
if (!EmailValidator.IsValidEmail(user.EmailAddress))
{
return Error($"The email address {user.EmailAddress} is not a valid format");
}
var ombiUser = new OmbiUser
{
Alias = user.Alias,
Email = user.EmailAddress,
UserName = user.Username,
UserType = UserType.LocalUser,
};
var userResult = await UserManager.CreateAsync(ombiUser, user.Password);
if (!userResult.Succeeded)
{
// We did not create the user
return new IdentityResult
{
Errors = userResult.Errors.Select(x => x.Description).ToList()
};
}
var roleResult = await AddRoles(user.Claims, ombiUser);
if (roleResult.Any(x => !x.Succeeded))
{
var messages = new List<string>();
foreach (var errors in roleResult.Where(x => !x.Succeeded))
{
messages.AddRange(errors.Errors.Select(x => x.Description).ToList());
}
return new IdentityResult
{
Errors = messages
};
}
return new IdentityResult
{
Successful = true
};
}
/// <summary>
/// This is for the local user to change their details.
/// </summary>
/// <param name="ui"></param>
/// <returns></returns>
[HttpPut("local")]
[Authorize]
public async Task<IdentityResult> UpdateLocalUser([FromBody] UpdateLocalUserModel ui)
{
if (string.IsNullOrEmpty(ui.CurrentPassword))
{
return Error("You need to provide your current password to make any changes");
}
var changingPass = !string.IsNullOrEmpty(ui.Password) || !string.IsNullOrEmpty(ui.ConfirmNewPassword);
if (changingPass)
{
if (!ui.Password.Equals(ui?.ConfirmNewPassword, StringComparison.CurrentCultureIgnoreCase))
{
return Error("Passwords do not match");
}
}
if (!EmailValidator.IsValidEmail(ui.EmailAddress))
{
return Error($"The email address {ui.EmailAddress} is not a valid format");
}
// Get the user
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == ui.Id);
if (user == null)
{
return Error("The user does not exist");
}
// Make sure the pass is ok
var passwordCheck = await UserManager.CheckPasswordAsync(user, ui.CurrentPassword);
if (!passwordCheck)
{
return Error("Your password is incorrect");
}
user.Email = ui.EmailAddress;
var updateResult = await UserManager.UpdateAsync(user);
if (!updateResult.Succeeded)
{
return new IdentityResult
{
Errors = updateResult.Errors.Select(x => x.Description).ToList()
};
}
if (changingPass)
{
var result = await UserManager.ChangePasswordAsync(user, ui.CurrentPassword, ui.Password);
if (!result.Succeeded)
{
return new IdentityResult
{
Errors = result.Errors.Select(x => x.Description).ToList()
};
}
}
return new IdentityResult
{
Successful = true
};
}
/// <summary>
/// Updates the user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name = "ui" > The user.</param>
/// <returns></returns>
//[HttpPut]
//public async Task<UserViewModel> UpdateUser([FromBody] UserViewModel user)
//{
// var userResult = await UserManager.UpdateUser(Mapper.Map<UserDto>(user));
// return Mapper.Map<UserViewModel>(userResult);
//}
///// <summary>
///// Deletes the user.
///// </summary>
///// <param name="user">The user.</param>
///// <returns></returns>
//[HttpDelete]
//public async Task<StatusCodeResult> DeleteUser([FromBody] UserViewModel user)
//{
// await UserManager.DeleteUser(Mapper.Map<UserDto>(user));
// return Ok();
//}
[HttpPut]
public async Task<IdentityResult> UpdateUser([FromBody] UserViewModel ui)
{
if (!EmailValidator.IsValidEmail(ui.EmailAddress))
{
return Error($"The email address {ui.EmailAddress} is not a valid format");
}
// Get the user
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == ui.Id);
user.Alias = ui.Alias;
user.Email = ui.EmailAddress;
var updateResult = await UserManager.UpdateAsync(user);
if (!updateResult.Succeeded)
{
return new IdentityResult
{
Errors = updateResult.Errors.Select(x => x.Description).ToList()
};
}
// Get the roles
var userRoles = await UserManager.GetRolesAsync(user);
foreach (var role in userRoles)
{
await UserManager.RemoveFromRoleAsync(user, role);
}
var result = await AddRoles(ui.Claims, user);
if (result.Any(x => !x.Succeeded))
{
var messages = new List<string>();
foreach (var errors in result.Where(x => !x.Succeeded))
{
messages.AddRange(errors.Errors.Select(x => x.Description).ToList());
}
return new IdentityResult
{
Errors = messages
};
}
return new IdentityResult
{
Successful = true
};
}
/// <summary>
/// Deletes the user.
/// </summary>
/// <param name="userId">The user.</param>
/// <returns></returns>
[HttpDelete("{userId}")]
public async Task<IdentityResult> DeleteUser(string userId)
{
var userToDelete = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
if (userToDelete != null)
{
var result = await UserManager.DeleteAsync(userToDelete);
if (result.Succeeded)
{
return new IdentityResult
{
Successful = true
};
}
return new IdentityResult
{
Errors = result.Errors.Select(x => x.Description).ToList()
};
}
return Error("Could not find user to delete.");
}
/// <summary>
/// Gets all available claims in the system.
/// </summary>
/// <returns></returns>
[HttpGet("claims")]
public IEnumerable<ClaimCheckboxes> GetAllClaims()
public async Task<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();
var claims = new List<ClaimCheckboxes>();
// Add the missing claims
var allRoles = await RoleManager.Roles.ToListAsync();
var missing = allRoles.Select(x => x.Name);
foreach (var role in missing)
{
claims.Add(new ClaimCheckboxes
{
Value = role,
Enabled = false
});
}
return claims;
}
return allClaims.Select(x => new ClaimCheckboxes() { Value = x });
private async Task<List<Microsoft.AspNetCore.Identity.IdentityResult>> AddRoles(IEnumerable<ClaimCheckboxes> roles, OmbiUser ombiUser)
{
var roleResult = new List<Microsoft.AspNetCore.Identity.IdentityResult>();
foreach (var role in roles)
{
if (role.Enabled)
{
roleResult.Add(await UserManager.AddToRoleAsync(ombiUser, role.Value));
}
}
return roleResult;
}
private IdentityResult Error(string message)
{
return new IdentityResult
{
Errors = new List<string> { message }
};
}
}
}

@ -1,4 +1,5 @@
using System.Collections.Generic;
using IdentityModel;
using IdentityServer4.Models;
namespace Ombi
@ -25,8 +26,8 @@ namespace Ombi
{
new ApiResource("api", "API")
{
UserClaims = {"role", "name"},
UserClaims = {JwtClaimTypes.Name, JwtClaimTypes.Role, JwtClaimTypes.Email, JwtClaimTypes.Id},
}
};
}

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Ombi.Models.Identity
{
public class IdentityResult
{
public List<string> Errors { get; set; }
public bool Successful { get; set; }
}
}

@ -0,0 +1,10 @@
using Ombi.Core.Models.UI;
namespace Ombi.Models.Identity
{
public class UpdateLocalUserModel : UserViewModel
{
public string CurrentPassword { get; set; }
public string ConfirmNewPassword { get; set; }
}
}
Loading…
Cancel
Save