Compare commits

25 Commits

Author SHA1 Message Date
7ea89aa35e chore(DI): bump to 1.1.3 2025-08-19 12:11:18 +02:00
50f5289191 refactor(BaseEntity): Aktualisierung des Namespace der BaseEntity für Multi-Targeting
- Anpassung des Namespace und der Klammern der Klasse „User“, um sowohl .NET als auch .NET Framework zu unterstützen.
- Beibehaltung bestehender Eigenschaften und Anmerkungen, einschließlich der Behandlung von Nullwerten und erforderlichen Angaben pro Ziel.
- Gewährleistet die korrekte Kompilierung unter den Bedingungen „#if NET“ und „#if NETFRAMEWORK“.
2025-08-19 11:54:57 +02:00
f8dcd5ba41 refactor(User): Aktualisierung des Namespace der User-Entität für Multi-Targeting
- Anpassung des Namespace und der Klammern der Klasse „User“, um sowohl .NET als auch .NET Framework zu unterstützen.
- Beibehaltung bestehender Eigenschaften und Anmerkungen, einschließlich der Behandlung von Nullwerten und erforderlichen Angaben pro Ziel.
- Gewährleistet die korrekte Kompilierung unter den Bedingungen „#if NET“ und „#if NETFRAMEWORK“.
2025-08-19 11:36:43 +02:00
8f5015f7d7 Refactor(Domain): Aktualisieren Sie die Framework-Bedingung, um das gesamte .NET zu unterstützen, anstatt nur .NET 7 oder höher. 2025-08-19 11:32:05 +02:00
efcd26fb29 bump to 6.1.4 2025-08-11 13:42:05 +02:00
2ee0f976fd refactor(cnnString): Remove user ID encryption 2025-08-11 12:59:00 +02:00
a3087a5e34 refactor(appsettings.Auth.json): change Audiance 2025-08-11 11:32:30 +02:00
525a30b541 refactor(DirectoryController): Ersetzen der Benutzersuche im Cache durch direkten DirectoryEntry aus Optionen
- Hinzufügen der Abhängigkeitsinjektion `IOptions<DirSearchRoot>`, um den vorkonfigurierten DirectoryEntry-Stamm zu verwenden.
- Einführung des Feldes `_dirSearchRoot` für wiederverwendbare LDAP-Root-Bindung.
- Ersetzen der Aufrufe von `FindAllByUserCache` durch `FindAll` unter Verwendung von `DirectoryEntry` aus `_dirSearchRoot`.
- Entfernen der veralteten Endpunkte `GetRootOf` und `CreateSearchRoot`.
- Sicherstellen des Musters `using var sRoot` für die ordnungsgemäße Entsorgung von DirectoryEntry-Instanzen.
2025-08-11 11:06:07 +02:00
f5471a8d01 feat(api): DirSearchRoot-Modell für die Konfiguration der LDAP-Verzeichnissuche und die Konvertierung zu DirectoryEntry hinzufügen 2025-08-11 10:51:58 +02:00
bcdcdd679a fix: version number 2025-08-08 13:38:43 +02:00
88cffc12a5 chore: add api-version and bind with footer 2025-08-08 13:26:15 +02:00
3bffdabc64 chore(api): bump to 6.1.3 2025-08-08 13:12:03 +02:00
5b45bdfa6b refactor(style.css): update to disable user selection 2025-08-08 12:59:40 +02:00
4f5d034e53 feat(footer): bind version with package.json 2025-08-08 12:55:05 +02:00
17d4bc51f4 refactor(privacy-policy): style: iframe-Styling zur Layoutanpassung aktualisiert
- Versionsinformationen zur Fußzeile hinzufügen
2025-08-08 12:16:54 +02:00
fc4209eb40 feat(footer): add the component with css 2025-08-08 11:12:00 +02:00
545b629129 refactor(privacy-policy.component): Aktualisiert, um iframe zu verwenden.
- Mit dem Pfad privacy-policy verknüpfen
2025-08-07 17:58:27 +02:00
eceace7e5e add mock privacy policy 2025-08-07 14:07:20 +02:00
cb9c7694db feat(privacy-policy.component): create privacy-polic using pdf-viewer 2025-08-07 13:42:48 +02:00
4c99e1cbee add ng2-pdf-viewer package 2025-08-07 13:36:06 +02:00
39c97e1e3c fix(group-update-form.component): update component to siable save button if the group is internal 2025-08-07 12:29:04 +02:00
720c25bd5c feat(api-service): Snackbar bei erfolgreichem Update anzeigen 2025-08-07 11:58:03 +02:00
ae0301c5be feat(group-update-form.component): update the component template to disable group name, comment, and aktiv components 2025-08-07 11:41:34 +02:00
8f31377b99 fix(GroupService): update to disable to change if the group is internal 2025-08-07 11:27:17 +02:00
Developer 02
addf7585f2 chore: configure for pnpm 2025-08-06 20:00:53 +02:00
41 changed files with 9758 additions and 224 deletions

View File

@@ -117,5 +117,8 @@
} }
} }
} }
},
"cli": {
"analytics": false
} }
} }

View File

@@ -1,6 +1,7 @@
{ {
"name": "user-manager-ui", "name": "user-manager-ui",
"version": "4.0.0", "version": "2.0.0",
"minApiVersion":"6.1.3",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start:ssl": "ng serve --ssl -o", "start:ssl": "ng serve --ssl -o",
@@ -31,9 +32,12 @@
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3", "bootstrap-icons": "^1.11.3",
"express": "^4.18.2", "express": "^4.18.2",
"moment": "^2.30.1",
"ng2-pdf-viewer": "^10.4.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"sweetalert2": "^11.12.3", "sweetalert2": "^11.12.3",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"uuid": "^11.1.0",
"zone.js": "~0.14.3" "zone.js": "~0.14.3"
}, },
"devDependencies": { "devDependencies": {

File diff suppressed because it is too large Load Diff

View File

@@ -3,5 +3,10 @@
"target": "https://localhost:7103", "target": "https://localhost:7103",
"secure": false, "secure": false,
"changeOrigin": true "changeOrigin": true
},
"/docs": {
"target": "https://localhost:7103",
"secure": false,
"changeOrigin": true
} }
} }

View File

@@ -1,4 +1,5 @@
<app-nav-menu></app-nav-menu> <app-nav-menu></app-nav-menu>
<main class="container-fluid"> <main class="container-fluid">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</main> </main>
<app-footer></app-footer>

View File

@@ -5,11 +5,12 @@ import { TransferService } from './services/button/transfer.service';
import { UpdateService } from './services/button/update.service'; import { UpdateService } from './services/button/update.service';
import { RefreshService } from './services/button/refresh.service'; import { RefreshService } from './services/button/refresh.service';
import { DeletionService } from './services/button/deletion.service'; import { DeletionService } from './services/button/deletion.service';
import { FooterComponent } from './components/footer/footer.component';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true, standalone: true,
imports: [RouterOutlet, NavMenuComponent], imports: [RouterOutlet, NavMenuComponent, FooterComponent],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.scss', styleUrl: './app.component.scss',
changeDetection: ChangeDetectionStrategy.Default changeDetection: ChangeDetectionStrategy.Default

View File

@@ -6,6 +6,7 @@ import { GroupComponent } from './pages/group/group.component';
import { ModuleComponent } from './pages/module/module.component'; import { ModuleComponent } from './pages/module/module.component';
import { UserAssignmentComponent } from './pages/user-assignment/user-assignment.component'; import { UserAssignmentComponent } from './pages/user-assignment/user-assignment.component';
import { UserRepresentationComponent } from './pages/user-representation/user-representation.component'; import { UserRepresentationComponent } from './pages/user-representation/user-representation.component';
import { PrivacyPolicyComponent } from './pages/privacy-policy/privacy-policy.component';
export const routes: Routes = [ export const routes: Routes = [
{ path: '', component: HomeComponent }, { path: '', component: HomeComponent },
@@ -13,5 +14,6 @@ export const routes: Routes = [
{ path: 'group-table', component: GroupComponent, canActivate: [AuthGuard] }, { path: 'group-table', component: GroupComponent, canActivate: [AuthGuard] },
{ path: 'module-table', component: ModuleComponent, canActivate: [AuthGuard] }, { path: 'module-table', component: ModuleComponent, canActivate: [AuthGuard] },
{ path: 'user-assignment', component: UserAssignmentComponent, canActivate: [AuthGuard] }, { path: 'user-assignment', component: UserAssignmentComponent, canActivate: [AuthGuard] },
{ path: 'user-representation', component: UserRepresentationComponent, canActivate: [AuthGuard] } { path: 'user-representation', component: UserRepresentationComponent, canActivate: [AuthGuard] },
{ path: 'privacy-policy', component: PrivacyPolicyComponent }
]; ];

View File

@@ -0,0 +1,10 @@
<footer>
<mat-toolbar color="mat-h3" class="footer-toolbar">
<a class="mat-h3 footer-brand-link" [routerLink]="['/']">&copy; 2025 Digital Data</a>
<div class="footer-links">
<a mat-button class="mat-body-strong" [routerLink]="['/privacy-policy']">Datenschutz</a>
<a mat-button class="mat-body-strong" href="https://digitaldata.works/#kontakt" target="_blank">Kontakt</a>
<span mat-button class="mat-body-strong">{{appVersion}}</span>
</div>
</mat-toolbar>
</footer>

View File

@@ -0,0 +1,23 @@
.footer-toolbar {
position: fixed;
bottom: 0;
width: 100%;
height: 2.2rem;
z-index: 1000;
align-items: center;
}
.footer-links{
display: flex;
padding: 0;
margin: 0;
justify-content: space-around;
width: 100%;
}
footer a {
padding: 0;
margin: 0;
text-decoration: none;
color: rgb(255, 255, 255);
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FooterComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { MatToolbarModule } from '@angular/material/toolbar';
import { RouterModule } from '@angular/router';
import { name, version, minApiVersion } from '../../../../package.json';
@Component({
selector: 'app-footer',
standalone: true,
imports: [
MatToolbarModule, RouterModule
],
templateUrl: './footer.component.html',
styleUrl: './footer.component.scss'
})
export class FooterComponent {
appVersion: string = `${name} v${version} | ${minApiVersion}`;
}

View File

@@ -42,7 +42,7 @@ export class GroupFormComponent {
.subscribe(() => this.updateErrorMessage()); .subscribe(() => this.updateErrorMessage());
} }
updateErrorMessage() { public updateErrorMessage() {
if (this.groupname.hasError('required')) { if (this.groupname.hasError('required')) {
this.errorMessage.set('Wert eingeben'); this.errorMessage.set('Wert eingeben');
} else { } else {

View File

@@ -7,20 +7,20 @@
</mat-form-field> </mat-form-field>
<mat-form-field class="w80p"> <mat-form-field class="w80p">
<mat-label>Gruppe</mat-label> <mat-label>Gruppe</mat-label>
<input matInput [formControl]="name" /> <input matInput [formControl]="name" [readonly] = 'group.internal' />
</mat-form-field> </mat-form-field>
</div> </div>
<!-- comment --> <!-- comment -->
<div class="dd-row input-row"> <div class="dd-row input-row">
<mat-form-field> <mat-form-field>
<mat-label>Kommentar</mat-label> <mat-label>Kommentar</mat-label>
<textarea matInput [formControl]="comment"></textarea> <textarea matInput [formControl]="comment" [readonly] = 'group.internal'></textarea>
</mat-form-field> </mat-form-field>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<!-- active, internal, async --> <!-- active, internal, async -->
<div class="dd-row input-row"> <div class="dd-row input-row">
<mat-slide-toggle [(ngModel)]="group.active"> <mat-slide-toggle [(ngModel)]="group.active" [disabled]='group.internal'>
Aktiv Aktiv
</mat-slide-toggle> </mat-slide-toggle>
<mat-slide-toggle [(ngModel)]="group.internal" disabled> <mat-slide-toggle [(ngModel)]="group.internal" disabled>
@@ -52,7 +52,7 @@
<mat-divider></mat-divider> <mat-divider></mat-divider>
<!-- save-button, delete-button --> <!-- save-button, delete-button -->
<div class="dd-row button-row"> <div class="dd-row button-row">
<button mat-fab extended (click)="update()"> <button mat-fab extended (click)="update()" [disabled]='group.internal'>
<mat-icon>save</mat-icon> <mat-icon>save</mat-icon>
Speichern Speichern
</button> </button>

View File

@@ -46,6 +46,7 @@ export class GroupUpdateFormComponent {
errorMessage = signal(''); errorMessage = signal('');
constructor(private uService: GroupService, private rService: RefreshService) { constructor(private uService: GroupService, private rService: RefreshService) {
console.log(this.group);
} }
update() { update() {

View File

@@ -0,0 +1 @@
<iframe src="docs/privacy-policy.pdf#toolbar=0&navpanes=0&scrollbar=1" ></iframe>

View File

@@ -0,0 +1,10 @@
iframe {
position : absolute;
padding: 0;
margin: 0;
left : 0;
top : 4rem;
width : 100%;
height : calc(100% - 6rem);
z-index : 10;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PrivacyPolicyComponent } from './privacy-policy.component';
describe('PrivacyPolicyComponent', () => {
let component: PrivacyPolicyComponent;
let fixture: ComponentFixture<PrivacyPolicyComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PrivacyPolicyComponent]
})
.compileComponents();
fixture = TestBed.createComponent(PrivacyPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
@Component({
selector: 'app-privacy-policy',
standalone: true,
imports: [],
templateUrl: './privacy-policy.component.html',
styleUrl: './privacy-policy.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PrivacyPolicyComponent {
}

View File

@@ -23,7 +23,7 @@ export interface Group {
id?: number; id?: number;
name?: string; name?: string;
adSync?: boolean; adSync?: boolean;
internal?: boolean; internal: boolean;
active?: boolean; active?: boolean;
comment?: string; comment?: string;
addedWho?: string; addedWho?: string;

View File

@@ -1,5 +1,7 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable, tap } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { inject } from '@angular/core';
export class ApiService<Model> { export class ApiService<Model> {
constructor(http: HttpClient, baseUrl: string) { constructor(http: HttpClient, baseUrl: string) {
@@ -9,6 +11,7 @@ export class ApiService<Model> {
http: HttpClient; http: HttpClient;
baseUrl: string; baseUrl: string;
protected snackBar = inject(MatSnackBar);
getAll(): Observable<Model[]> { getAll(): Observable<Model[]> {
return this.http.get<Model[]>(this.baseUrl, { withCredentials: true }); return this.http.get<Model[]>(this.baseUrl, { withCredentials: true });
@@ -25,7 +28,12 @@ export class ApiService<Model> {
update(updateModel: Model): Observable<Model> { update(updateModel: Model): Observable<Model> {
const url = `${this.baseUrl}`; const url = `${this.baseUrl}`;
return this.http.put<Model>(url, updateModel, { withCredentials: true }); return this.http.put<Model>(url, updateModel, { withCredentials: true }).pipe(
tap(() => {
this.snackBar.open('Aktualisierung erfolgreich!', 'Schließen', {
duration: 2000,
});
}));
} }
delete(id: number): Observable<any> { delete(id: number): Observable<any> {

View File

@@ -1,5 +1,8 @@
import { Version } from "@angular/core";
export const env = { export const env = {
production: false, production: true,
version: new Version("6.1.3"),
default_api_url: "/api", default_api_url: "/api",
routes: { routes: {
user: "/user", user: "/user",

View File

@@ -2,6 +2,13 @@
@import '../node_modules/@angular/cdk/overlay-prebuilt.css'; @import '../node_modules/@angular/cdk/overlay-prebuilt.css';
@import "../node_modules/bootstrap-icons/font/bootstrap-icons.css"; @import "../node_modules/bootstrap-icons/font/bootstrap-icons.css";
* {
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Standart */
}
a { a {
color: #0366d6; color: #0366d6;
} }

View File

@@ -21,7 +21,8 @@
"lib": [ "lib": [
"ES2022", "ES2022",
"dom" "dom"
] ],
"resolveJsonModule": true
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false, "enableI18nLegacyMessageIdFormat": false,

View File

@@ -9,6 +9,8 @@ using Microsoft.Extensions.Localization;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using DigitalData.Core.Abstraction.Application; using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.DTO; using DigitalData.Core.Abstraction.Application.DTO;
using Microsoft.Extensions.Options;
using DigitalData.UserManager.API.Models;
namespace DigitalData.UserManager.API.Controllers; namespace DigitalData.UserManager.API.Controllers;
@@ -23,8 +25,9 @@ public class DirectoryController : ControllerBase
private readonly Dictionary<string, string> _customSearchFilters; private readonly Dictionary<string, string> _customSearchFilters;
private readonly IStringLocalizer<Resource> _localizer; private readonly IStringLocalizer<Resource> _localizer;
private readonly ILogger<DirectoryController> _logger; private readonly ILogger<DirectoryController> _logger;
private readonly DirSearchRoot _dirSearchRoot;
public DirectoryController(IConfiguration configuration, IStringLocalizer<Resource> localizer, IUserService userService, IDirectorySearchService directorySearchService, ILogger<DirectoryController> logger) public DirectoryController(IConfiguration configuration, IStringLocalizer<Resource> localizer, IUserService userService, IDirectorySearchService directorySearchService, ILogger<DirectoryController> logger, IOptions<DirSearchRoot> dirSearchRootOptions)
{ {
_localizer = localizer; _localizer = localizer;
_userService = userService; _userService = userService;
@@ -33,23 +36,7 @@ public class DirectoryController : ControllerBase
var customSearchFiltersSection = configuration.GetSection("DirectorySearch:CustomSearchFilters"); var customSearchFiltersSection = configuration.GetSection("DirectorySearch:CustomSearchFilters");
_customSearchFilters = customSearchFiltersSection.Get<Dictionary<string, string>>() ?? new(); _customSearchFilters = customSearchFiltersSection.Get<Dictionary<string, string>>() ?? new();
_logger = logger; _logger = logger;
} _dirSearchRoot = dirSearchRootOptions.Value;
[HttpGet("Root/{username}")]
public IActionResult GetRootOf(string username)
{
var root = _dirSearchService.GetSearchRootCache(username);
return root is null ? NotFound() : Ok(new
{
guid = root.Guid,
nativeGuid = root.NativeGuid,
name = root.Name,
path = root.Path,
parentPath = root.Parent?.Path,
username = root.Username,
schemaClassName = root.SchemaClassName
});
} }
[HttpGet("CustomSearchFilter")] [HttpGet("CustomSearchFilter")]
@@ -66,26 +53,6 @@ public class DirectoryController : ControllerBase
} }
} }
[HttpPost]
public async Task<IActionResult> CreateSearchRoot([FromBody] SearchRootCreateDto searchRootCreateDto)
{
var dirEntryUsername = searchRootCreateDto.Username ?? CurrentUser;
if (dirEntryUsername is null)
return Unauthorized();
bool isValid = _dirSearchService.ValidateCredentials(dirEntryUsername, searchRootCreateDto.Password);
if (!isValid)
return Unauthorized(Result.Fail().Message(_localizer[Key.UserNotFound]));
var userResult = await _userService.ReadByUsernameAsync(dirEntryUsername);
if (!userResult.IsSuccess || userResult.Data is null)
return Unauthorized(Result.Fail().Message(_localizer[Key.UserNotFoundInLocalDB]));
_dirSearchService.SetSearchRootCache(userResult.Data.Username, searchRootCreateDto.Password);
return Ok();
}
[HttpGet("SearchByFilter/{filter}")] [HttpGet("SearchByFilter/{filter}")]
public IActionResult SearchByFilter([FromRoute] string filter, string? dirEntryUsername, params string[] propName) public IActionResult SearchByFilter([FromRoute] string filter, string? dirEntryUsername, params string[] propName)
{ {
@@ -94,7 +61,9 @@ public class DirectoryController : ControllerBase
if (dirEntryUsername is null) if (dirEntryUsername is null)
return Unauthorized(); return Unauthorized();
return _dirSearchService.FindAllByUserCache(dirEntryUsername, filter, properties: propName).Then(Ok, IActionResult (m, n) => using var sRoot = _dirSearchRoot.ToDirectoryEntry;
return _dirSearchService.FindAll(sRoot, filter, properties: propName).Then(Ok, IActionResult (m, n) =>
{ {
_logger.LogNotice(n); _logger.LogNotice(n);
return StatusCode(StatusCodes.Status424FailedDependency); return StatusCode(StatusCodes.Status424FailedDependency);
@@ -114,7 +83,9 @@ public class DirectoryController : ControllerBase
if (filter is null) if (filter is null)
return NotFound($"The filter named {filterName} does not exist."); return NotFound($"The filter named {filterName} does not exist.");
return _dirSearchService.FindAllByUserCache(dirEntryUsername, filter, properties: propName).Then(Ok, IActionResult (m, n) => using var sRoot = _dirSearchRoot.ToDirectoryEntry;
return _dirSearchService.FindAll(sRoot, filter, properties: propName).Then(Ok, IActionResult (m, n) =>
{ {
_logger.LogNotice(n); _logger.LogNotice(n);
return StatusCode(StatusCodes.Status424FailedDependency); return StatusCode(StatusCodes.Status424FailedDependency);
@@ -135,7 +106,9 @@ public class DirectoryController : ControllerBase
if (filter is null) if (filter is null)
throw new InvalidOperationException("The LDAP Group Search filter configuration is missing in your appsettings. Please ensure it's added under DirectorySearch:CustomSearchFilters:Group to enable group searches."); throw new InvalidOperationException("The LDAP Group Search filter configuration is missing in your appsettings. Please ensure it's added under DirectorySearch:CustomSearchFilters:Group to enable group searches.");
return _dirSearchService.FindAllByUserCache(username: dirEntryUsername, filter, properties: propName).Then(Ok, IActionResult (m, n) => using var sRoot = _dirSearchRoot.ToDirectoryEntry;
return _dirSearchService.FindAll(_dirSearchRoot.ToDirectoryEntry, filter, properties: propName).Then(Ok, IActionResult (m, n) =>
{ {
_logger.LogNotice(n); _logger.LogNotice(n);
return StatusCode(StatusCodes.Status424FailedDependency); return StatusCode(StatusCodes.Status424FailedDependency);
@@ -156,7 +129,9 @@ public class DirectoryController : ControllerBase
if (filter is null) if (filter is null)
throw new InvalidOperationException("The LDAP User Search filter configuration is missing in your appsettings. Please ensure it's added under DirectorySearch:CustomSearchFilters:User to enable group searches."); throw new InvalidOperationException("The LDAP User Search filter configuration is missing in your appsettings. Please ensure it's added under DirectorySearch:CustomSearchFilters:User to enable group searches.");
return _dirSearchService.FindAllByUserCache(username: dirEntryUsername, filter, properties: propName).Then( using var sRoot = _dirSearchRoot.ToDirectoryEntry;
return _dirSearchService.FindAll(sRoot, filter, properties: propName).Then(
Success: data => Success: data =>
{ {
if (groupName is not null) if (groupName is not null)

View File

@@ -4,29 +4,12 @@
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks> <TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Version>6.1.2</Version> <Version>6.1.4</Version>
<AssemblyVersion>6.1.2</AssemblyVersion> <AssemblyVersion>6.1.4</AssemblyVersion>
<FileVersion>6.1.2</FileVersion> <FileVersion>6.1.4</FileVersion>
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="wwwroot\assets\**" />
<Compile Remove="wwwroot\media\**" />
<Content Remove="wwwroot\assets\**" />
<Content Remove="wwwroot\media\**" />
<EmbeddedResource Remove="wwwroot\assets\**" />
<EmbeddedResource Remove="wwwroot\media\**" />
<None Remove="wwwroot\assets\**" />
<None Remove="wwwroot\media\**" />
</ItemGroup>
<ItemGroup>
<Content Remove="wwwroot\chunk-A2L6DXQH.js" />
<Content Remove="wwwroot\chunk-ZC35XWOR.js" />
<Content Remove="wwwroot\main-QF3MRK45.js" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" /> <PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.0.0" /> <PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.0.0" />
@@ -47,7 +30,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="ClientApp\" /> <Folder Include="ClientApp\" />
<Folder Include="wwwroot\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -8,5 +8,5 @@ public class AuthTokenKeys
public string Issuer { get; init; } = "auth.digitaldata.works"; public string Issuer { get; init; } = "auth.digitaldata.works";
public string Audience { get; init; } = "user-manager.digitaldata.works"; public string Audience { get; init; } = "usermanager.digitaldata.works";
} }

View File

@@ -0,0 +1,22 @@
using DigitalData.Core.Application;
using System.DirectoryServices;
namespace DigitalData.UserManager.API.Models;
[Obsolete("Use ActiveDirectory.API")]
public class DirSearchRoot : DirectorySearchOptions
{
public string Path => $"LDAP://{ServerName}/{Root}";
public string? Username { get; set; }
public string? Password { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public DirectoryEntry ToDirectoryEntry => new ()
{
Path = Path,
Username = Username,
Password = Password
};
}

View File

@@ -82,6 +82,7 @@ try {
builder.ConfigureBySection<DirectorySearchOptions>(); builder.ConfigureBySection<DirectorySearchOptions>();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions")); builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
builder.Services.Configure<DirSearchRoot>(config.GetSection("DirectorySearchOptions"));
builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor() builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor()
{ {
Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object) Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object)
@@ -171,7 +172,6 @@ try {
var eCnnStr = config.GetConnectionString("UM_DEF") ?? throw new InvalidOperationException("Connection string 'DD_ECM_Connection' is missing from the configuration."); var eCnnStr = config.GetConnectionString("UM_DEF") ?? throw new InvalidOperationException("Connection string 'DD_ECM_Connection' is missing from the configuration.");
SqlConnectionStringBuilder cnnStrBuilder = new(eCnnStr); SqlConnectionStringBuilder cnnStrBuilder = new(eCnnStr);
cnnStrBuilder.UserID = encryptor.Decrypt(cnnStrBuilder.UserID);
cnnStrBuilder.Password = encryptor.Decrypt(cnnStrBuilder.Password); cnnStrBuilder.Password = encryptor.Decrypt(cnnStrBuilder.Password);
var dCnnStr = cnnStrBuilder.ConnectionString; var dCnnStr = cnnStrBuilder.ConnectionString;
return dCnnStr; return dCnnStr;

View File

@@ -4,7 +4,7 @@
"PublicKeys": [ "PublicKeys": [
{ {
"Issuer": "auth.digitaldata.works", "Issuer": "auth.digitaldata.works",
"Audience": "user-manager.digitaldata.works" "Audience": "usermanager.digitaldata.works"
} }
], ],
"RetryDelay": "00:00:05" "RetryDelay": "00:00:05"

View File

@@ -6,14 +6,15 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"UM_DEF": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=g+2edXEbMbujCUjh7INZRQ==;Password=Bz/n9pu8EyzlVqicaMRQGQ==;Encrypt=false;TrustServerCertificate=True;" "UM_DEF": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=Bz/n9pu8EyzlVqicaMRQGQ==;Encrypt=false;TrustServerCertificate=True;"
}, },
"AllowedOrigins": [ "https://localhost:7103", "http://172.24.12.39:85", "http://localhost:85", "http://localhost:4200", "http://localhost:5500", "https://localhost:7202" ], "AllowedOrigins": [ "https://localhost:7103", "http://172.24.12.39:85", "http://localhost:85", "http://localhost:4200", "http://localhost:5500", "https://localhost:7202" ],
"RunAsWindowsService": false, "RunAsWindowsService": false,
"DirectorySearchOptions": { "DirectorySearchOptions": {
"ServerName": "DD-VMP01-DC01", "ServerName": "DD-VMP01-DC01",
"Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works", "Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works",
"UserCacheExpirationDays": 1, "Username": "FABRIK19-User01",
"Password": "9bWOr0UGuHn_7VkC",
"CustomSearchFilters": { "CustomSearchFilters": {
"User": "(&(objectClass=user)(sAMAccountName=*))", "User": "(&(objectClass=user)(sAMAccountName=*))",
"Group": "(&(objectClass=group) (samAccountName=*))" "Group": "(&(objectClass=group) (samAccountName=*))"

View File

@@ -43,7 +43,7 @@ namespace DigitalData.UserManager.Application.Services
var group = await _repository.ReadByIdAsync(gId); var group = await _repository.ReadByIdAsync(gId);
if (group is null) if (group is null)
throw new ForbiddenException("Group not found."); throw new ForbiddenException("Group not found.");
else if (!group.Internal) else if (group.Internal)
throw new ForbiddenException("Updates are not allowed for system groups."); throw new ForbiddenException("Updates are not allowed for system groups.");
return await base.UpdateAsync(updateDto); return await base.UpdateAsync(updateDto);
} }

View File

@@ -13,9 +13,9 @@
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebUserManager.git</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/WebUserManager.git</RepositoryUrl>
<PackageTags>digital data user maanger</PackageTags> <PackageTags>digital data user maanger</PackageTags>
<Version>1.1.2</Version> <Version>1.1.3</Version>
<AssemblyVersion>1.1.2</AssemblyVersion> <AssemblyVersion>1.1.3</AssemblyVersion>
<FileVersion>1.1.2</FileVersion> <FileVersion>1.1.3</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -6,36 +6,44 @@ using System;
#endif #endif
namespace DigitalData.UserManager.Domain.Entities namespace DigitalData.UserManager.Domain.Entities
{ #if NET
public class BaseEntity ;
#elif NETFRAMEWORK
{ {
[Column("GUID")]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(50)]
[Column("ADDED_WHO")]
public string
#if NET7_0_OR_GREATER
?
#endif #endif
AddedWho { get; set; }
[StringLength(50)] public class BaseEntity
[Column("CHANGED_WHO")] {
public string [Column("GUID")]
#if NET7_0_OR_GREATER [Key]
? [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(50)]
[Column("ADDED_WHO")]
public string
#if NET
?
#endif #endif
ChangedWho { get; set; } AddedWho { get; set; }
//TODO: assign it to default value in create dto, not here! [StringLength(50)]
[Column("ADDED_WHEN", TypeName = "datetime")] [Column("CHANGED_WHO")]
[DefaultValue("GETDATE()")] public string
public DateTime AddedWhen { get; set; } = DateTime.Now; #if NET
?
#endif
ChangedWho { get; set; }
[Column("CHANGED_WHEN", TypeName = "datetime")] //TODO: assign it to default value in create dto, not here!
public DateTime? ChangedWhen { get; set; } [Column("ADDED_WHEN", TypeName = "datetime")]
[DefaultValue("GETDATE()")]
public DateTime AddedWhen { get; set; } = DateTime.Now;
[Column("CHANGED_WHEN", TypeName = "datetime")]
public DateTime? ChangedWhen { get; set; }
}
#if NETFRAMEWORK
} }
} #endif

View File

@@ -25,7 +25,7 @@ namespace DigitalData.UserManager.Domain.Entities
[Column("COMMENT")] [Column("COMMENT")]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Comment { get; set; } Comment { get; set; }
@@ -33,7 +33,7 @@ namespace DigitalData.UserManager.Domain.Entities
[StringLength(50)] [StringLength(50)]
[Column("ADDED_WHO")] [Column("ADDED_WHO")]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
AddedWho { get; set; } AddedWho { get; set; }

View File

@@ -9,7 +9,7 @@ namespace DigitalData.UserManager.Domain.Entities
{ {
[StringLength(50)] [StringLength(50)]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Name { get; set; } Name { get; set; }
@@ -29,7 +29,7 @@ namespace DigitalData.UserManager.Domain.Entities
[StringLength(200)] [StringLength(200)]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Comment { get; set; } Comment { get; set; }

View File

@@ -16,21 +16,21 @@ namespace DigitalData.UserManager.Domain.Entities
[StringLength(200)] [StringLength(200)]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Comment { get; set; } Comment { get; set; }
[ForeignKey("UserId")] [ForeignKey("UserId")]
public virtual User public virtual User
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
User { get; set; } User { get; set; }
[ForeignKey("GroupId")] [ForeignKey("GroupId")]
public virtual Group public virtual Group
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Group { get; set; } Group { get; set; }

View File

@@ -13,7 +13,7 @@ namespace DigitalData.UserManager.Domain.Entities
[StringLength(50)] [StringLength(50)]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Name { get; set; } Name { get; set; }
@@ -21,7 +21,7 @@ namespace DigitalData.UserManager.Domain.Entities
[StringLength(20)] [StringLength(20)]
[Column("SHORT_NAME")] [Column("SHORT_NAME")]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
ShortName { get; set; } ShortName { get; set; }

View File

@@ -22,7 +22,7 @@ namespace DigitalData.UserManager.Domain.Entities
[Column("COMMENT")] [Column("COMMENT")]
[StringLength(200)] [StringLength(200)]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Comment { get; set; } Comment { get; set; }
@@ -30,7 +30,7 @@ namespace DigitalData.UserManager.Domain.Entities
[Column("ADDED_WHO")] [Column("ADDED_WHO")]
[StringLength(50)] [StringLength(50)]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
AddedWho { get; set; } = "DEFAULT"; AddedWho { get; set; } = "DEFAULT";
@@ -38,21 +38,21 @@ namespace DigitalData.UserManager.Domain.Entities
[Column("CHANGED_WHO")] [Column("CHANGED_WHO")]
[StringLength(50)] [StringLength(50)]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
ChangedWho { get; set; } ChangedWho { get; set; }
[ForeignKey("UserId")] [ForeignKey("UserId")]
public virtual User public virtual User
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
User { get; set; } User { get; set; }
[ForeignKey("ModuleId")] [ForeignKey("ModuleId")]
public virtual Module public virtual Module
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Module { get; set; } Module { get; set; }

View File

@@ -6,122 +6,130 @@ using System;
#endif #endif
namespace DigitalData.UserManager.Domain.Entities namespace DigitalData.UserManager.Domain.Entities
{ #if NET
[Table("TBDD_USER", Schema = "dbo")] ;
public partial class User : BaseEntity #elif NETFRAMEWORK
{ {
[Column("PRENAME")]
[StringLength(50)]
public string
#if NET7_0_OR_GREATER
?
#endif #endif
Prename { get; set; }
[Column("NAME")] [Table("TBDD_USER", Schema = "dbo")]
[StringLength(50)] public partial class User : BaseEntity
public string {
#if NET7_0_OR_GREATER [Column("PRENAME")]
? [StringLength(50)]
public string
#if NET
?
#endif #endif
Name { get; set; } Prename { get; set; }
[Required] [Column("NAME")]
[Column("USERNAME")] [StringLength(50)]
[StringLength(50)] public string
public #if NET
#if NET7_0_OR_GREATER ?
required
#endif #endif
string Username { get; set; } Name { get; set; }
[Column("SHORTNAME")] [Required]
[StringLength(30)] [Column("USERNAME")]
public string [StringLength(50)]
#if NET7_0_OR_GREATER public
? #if NET
required
#endif #endif
Shortname string Username { get; set; }
{ get; set; }
[Column("EMAIL")] [Column("SHORTNAME")]
[StringLength(100)] [StringLength(30)]
public string public string
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Email { get; set; } Shortname
{ get; set; }
[Required] [Column("EMAIL")]
[Column("LANGUAGE")] [StringLength(100)]
[StringLength(5)] public string
[DefaultValue("de-DE")] #if NET
public string ?
#if NET7_0_OR_GREATER
?
#endif #endif
Language { get; set; } Email { get; set; }
[Column("COMMENT")] [Required]
[StringLength(500)] [Column("LANGUAGE")]
public string [StringLength(5)]
#if NET7_0_OR_GREATER [DefaultValue("de-DE")]
? public string
#if NET
?
#endif #endif
Comment { get; set; } Language { get; set; }
[Column("DELETED")] [Column("COMMENT")]
public bool Deleted { get; set; } [StringLength(500)]
public string
[Required] #if NET
[Column("DATE_FORMAT")] ?
[StringLength(10)]
[DefaultValue("dd.MM.yyyy")]
public
#if NET7_0_OR_GREATER
required
#endif #endif
string DateFormat { get; set; } Comment { get; set; }
[Required] [Column("DELETED")]
[Column("ACTIVE")] public bool Deleted { get; set; }
public bool Active { get; set; }
[Required] [Required]
[Column("GENERAL_VIEWER")] [Column("DATE_FORMAT")]
[StringLength(30)] [StringLength(10)]
[DefaultValue("NONE")] [DefaultValue("dd.MM.yyyy")]
public public
#if NET7_0_OR_GREATER #if NET
required required
#endif #endif
string GeneralViewer { get; set; } string DateFormat { get; set; }
[Required] [Required]
[Column("WAN_ENVIRONMENT")] [Column("ACTIVE")]
public bool WanEnvironment { get; set; } public bool Active { get; set; }
[Required] [Required]
[Column("USERID_FK_INT_ECM")] [Column("GENERAL_VIEWER")]
public int UserIdFkIntEcm { get; set; } [StringLength(30)]
[DefaultValue("NONE")]
[Column("DELETED_WHEN")] public
public DateTime #if NET
#if NET7_0_OR_GREATER required
?
#endif #endif
DeletedWhen { get; set; } string GeneralViewer { get; set; }
[Column("DELETED_WHO")] [Required]
[StringLength(50)] [Column("WAN_ENVIRONMENT")]
public public bool WanEnvironment { get; set; }
#if NET7_0_OR_GREATER
required [Required]
[Column("USERID_FK_INT_ECM")]
public int UserIdFkIntEcm { get; set; }
[Column("DELETED_WHEN")]
public DateTime
#if NET
?
#endif #endif
string DeletedWhen { get; set; }
#if NET7_0_OR_GREATER
? [Column("DELETED_WHO")]
# endif [StringLength(50)]
DeletedWho { get; set; } public
#if NET
required
#endif
string
#if NET
?
#endif
DeletedWho { get; set; }
}
#if NETFRAMEWORK
} }
} #endif

View File

@@ -28,28 +28,28 @@ namespace DigitalData.UserManager.Domain.Entities
[ForeignKey("UserId")] [ForeignKey("UserId")]
public virtual User public virtual User
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
User { get; set; } User { get; set; }
[ForeignKey("RepGroupId")] [ForeignKey("RepGroupId")]
public virtual Group public virtual Group
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
RepGroup { get; set; } RepGroup { get; set; }
[ForeignKey("GroupId")] [ForeignKey("GroupId")]
public virtual Group public virtual Group
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
Group { get; set; } Group { get; set; }
[ForeignKey("RepUserId")] [ForeignKey("RepUserId")]
public virtual User public virtual User
#if NET7_0_OR_GREATER #if NET
? ?
#endif #endif
RepUser { get; set; } RepUser { get; set; }