@cyco77/pptb-user-security-utility 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +506 -0
- package/dist/assets/index-ys3HPQ3U.js +11 -0
- package/dist/assets/user-team-security_small-B9V3XVfW.png +0 -0
- package/dist/index.html +14 -0
- package/index.html +12 -0
- package/npm-shrinkwrap.json +3544 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
# User & Team Security Documentation Generator
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="icon/user-team-security_small.png" alt="User & Team Security Logo">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
A Power Platform Tool Box (PPTB) tool for viewing and documenting Dynamics 365 user and team security configurations. This tool provides an intuitive interface to explore security roles, team memberships, and export documentation in multiple formats.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
## Screenshots
|
|
12
|
+
|
|
13
|
+
### Dark Theme
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
### Light Theme
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
### Core Capabilities
|
|
24
|
+
|
|
25
|
+
- � **User & Team Browser** - View all system users and teams in your Dynamics 365 environment
|
|
26
|
+
- 🔐 **Security Role Details** - Display directly assigned security roles for users and teams
|
|
27
|
+
- 👔 **Team Membership** - View all team memberships for system users
|
|
28
|
+
- 🔍 **Advanced Filtering**:
|
|
29
|
+
- Filter by entity type (System Users / Teams)
|
|
30
|
+
- Filter users by status (All / Enabled / Disabled)
|
|
31
|
+
- Filter users by type (All / Users / Applications) - defaults to regular users
|
|
32
|
+
- Filter by business unit
|
|
33
|
+
- Real-time text search across names and domains
|
|
34
|
+
- 📊 **Interactive Data Grid**:
|
|
35
|
+
- Single-row selection to view details
|
|
36
|
+
- Sortable columns with default sorting by name
|
|
37
|
+
- Resizable columns
|
|
38
|
+
- 📱 **Detail Side Panel** - Displays selected user/team with:
|
|
39
|
+
- All directly assigned security roles
|
|
40
|
+
- Team memberships (for users)
|
|
41
|
+
- Business unit information
|
|
42
|
+
- Managed role indicators
|
|
43
|
+
- 📤 **Multiple Export Formats**:
|
|
44
|
+
- **CSV Matrix Export** - Roles and teams as columns, users as rows, 'X' marks assignments
|
|
45
|
+
- **Markdown Export** - Copy formatted documentation to clipboard
|
|
46
|
+
- 📢 **Visual Notifications** - Toast notifications for successful exports
|
|
47
|
+
- 🎨 **Theme Support** - Automatic light/dark theme switching based on PPTB settings
|
|
48
|
+
|
|
49
|
+
### Technical Stack
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
- ✅ user-security-utility/
|
|
53
|
+
├── src/
|
|
54
|
+
│ ├── components/
|
|
55
|
+
│ │ ├── DataGridView.tsx # Data grid for users/teams with selection
|
|
56
|
+
│ │ ├── Filter.tsx # Entity type, status, user type, and business unit filtering
|
|
57
|
+
│ │ ├── Overview.tsx # Main container component with export functionality
|
|
58
|
+
│ │ └── SecurityRolesPanel.tsx # Side panel displaying roles and team memberships
|
|
59
|
+
│ ├── hooks/
|
|
60
|
+
│ │ ├── useConnection.ts # Dataverse connection management
|
|
61
|
+
│ │ ├── useToolboxAPI.ts # Toolbox API hook
|
|
62
|
+
│ │ └── useToolboxEvents.ts # PPTB event subscription
|
|
63
|
+
│ ├── services/
|
|
64
|
+
│ │ ├── dataverseService.ts # Dataverse API queries for users, teams, and roles
|
|
65
|
+
│ │ └── loggerService.ts # Centralized logging singleton
|
|
66
|
+
│ ├── types/
|
|
67
|
+
│ │ ├── systemUser.ts # System user type definitions
|
|
68
|
+
│ │ ├── team.ts # Team type definitions
|
|
69
|
+
│ │ └── securityRole.ts # Security role type definitions
|
|
70
|
+
│ ├── App.tsx # Main application component
|
|
71
|
+
│ ├── main.tsx # Entry point
|
|
72
|
+
│ └── index.css # Global styling
|
|
73
|
+
├── icon/ # Application icons
|
|
74
|
+
├── screenshots/ # Screenshots for documentationep data to models
|
|
75
|
+
├── index.html
|
|
76
|
+
├── package.json
|
|
77
|
+
├── tsconfig.json
|
|
78
|
+
└── vite.config.ts
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
### Prerequisites
|
|
85
|
+
|
|
86
|
+
- Node.js >= 18.0.0
|
|
87
|
+
- npm oruser-security-utility
|
|
88
|
+
- Power Platform Toolbox installed
|
|
89
|
+
|
|
90
|
+
### Setup
|
|
91
|
+
|
|
92
|
+
1. Clone the repository:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git clone <repository-url>
|
|
96
|
+
cd pptb-plugin-documentation-generator
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
2. Install dependencies:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm install
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
### Development Server
|
|
108
|
+
|
|
109
|
+
Start development server with HMR:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run dev
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The tool will be available at `http://localhost:5173`
|
|
116
|
+
|
|
117
|
+
### Watch Mode
|
|
118
|
+
|
|
119
|
+
Build the tool in watch mode for continuous updates:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm run watch
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Production Build
|
|
126
|
+
|
|
127
|
+
Build the optimized production version:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npm run build
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The output will be in the `dist/` directory.
|
|
134
|
+
|
|
135
|
+
### Preview Build
|
|
136
|
+
|
|
137
|
+
Preview the production build locally:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm run preview
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Usage
|
|
144
|
+
|
|
145
|
+
### In Power Platform Toolbox
|
|
146
|
+
|
|
147
|
+
1. Build the tool:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm run build
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
2. Package the tool (creates npm-shrinkwrap.json):
|
|
154
|
+
tool to view users and teams
|
|
155
|
+
|
|
156
|
+
### User Interface
|
|
157
|
+
|
|
158
|
+
#### Filter Section
|
|
159
|
+
|
|
160
|
+
- **Entity Type Dropdown**: Switch between System Users and Teams
|
|
161
|
+
- **Status Filter** (Users only): All / Enabled / Disabled
|
|
162
|
+
- **User Type Filter** (Users only): All / Users / Applications (defaults to Users)
|
|
163
|
+
- **Business Unit Dropdown**: Filter by business unit
|
|
164
|
+
- **Search Box**: Real-time search across user/team names, domain names, and business units
|
|
165
|
+
|
|
166
|
+
#### Export Buttons (Users only)
|
|
167
|
+
|
|
168
|
+
- **Export to CSV**: Downloads a matrix-style CSV where:
|
|
169
|
+
- Rows represent users
|
|
170
|
+
- Columns represent security roles and team memberships
|
|
171
|
+
- 'X' marks indicate assignments
|
|
172
|
+
- Includes user details (name, domain, business unit, status)
|
|
173
|
+
- **Copy as Markdown**: Copies detailed report to clipboard with:
|
|
174
|
+
- Hierarchical structure per user
|
|
175
|
+
- Security roles with managed status and business unit
|
|
176
|
+
- Team memberships with type and default indicators
|
|
177
|
+
|
|
178
|
+
#### Data Grid
|
|
179
|
+
|
|
180
|
+
- Click any row to select and view details
|
|
181
|
+
- Selected row is highlighted
|
|
182
|
+
- Click column headers to sort (default: sorted by name)
|
|
183
|
+
- Supports both System Users and Teams views
|
|
184
|
+
|
|
185
|
+
#### Security Details Panel
|
|
186
|
+
|
|
187
|
+
Appears on the right when a user or team is selected:
|
|
188
|
+
|
|
189
|
+
- **Header**: Shows user/team name
|
|
190
|
+
- **Security Roles Section**: Lists all directly assigned security roles with:
|
|
191
|
+
- Role name
|
|
192
|
+
- "Managed" badge for managed roles
|
|
193
|
+
- Business unit information
|
|
194
|
+
- **Team Memberships Section** (Users only): Lists all team memberships with:
|
|
195
|
+
- Team name
|
|
196
|
+
- Team type (Owner/Access)
|
|
197
|
+
- "Default" badge for default teams
|
|
198
|
+
- Business unit information
|
|
199
|
+
- Click column headers to sort
|
|
200
|
+
- Drag column borders to resize
|
|
201
|
+
- View tooltips on hover for full text content
|
|
202
|
+
|
|
203
|
+
#### Evensystem users
|
|
204
|
+
|
|
205
|
+
const users = await window.dataverseAPI.queryData(
|
|
206
|
+
"systemusers?$select=systemuserid,fullname,domainname,isdisabled,applicationid&$expand=businessunitid($select=businessunitid,name)&$orderby=fullname"
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Query teams
|
|
210
|
+
const teams = await window.dataverseAPI.queryData(
|
|
211
|
+
"teams?$select=teamid,name,teamtype,isdefault&$expand=businessunitid($select=businessunitid,name)&$orderby=name"
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Query security roles for a user
|
|
215
|
+
const roles = await window.dataverseAPI.queryData(
|
|
216
|
+
`systemusers(${userId})/systemuserroles_association?$select=roleid,name,ismanaged&$expand=businessunitid($select=businessunitid,name)`
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Query teams for a user
|
|
220
|
+
const userTeams = await window.dataverseAPI.queryData(
|
|
221
|
+
`systemusers(${userId})/teammembership_association?$select=teamid,name,teamtype,isdefault&$expand=businessunitid($select=businessunitid,name)
|
|
222
|
+
The tool demonstrates various Power Platform Toolbox and Dataverse API features:
|
|
223
|
+
|
|
224
|
+
### Connection Management
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// Get current connection
|
|
228
|
+
const connection = await window.toolboxAPI.getConnection();
|
|
229
|
+
console.log(connection.connectionUrl);
|
|
230
|
+
|
|
231
|
+
// Listen for connection changes
|
|
232
|
+
window.toUser security data has been exported to CSV successfully."ata with new connection
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Dataverse Queries
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// Query plugin assemblies
|
|
241
|
+
const assemblies = await window.dataverseAPI.executeQuery(
|
|
242
|
+
`pluginassemblies?$select=name,versio
|
|
243
|
+
"user-security-export-2025-12-23.csv",
|
|
244
|
+
csvContent
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Copy to clipboard
|
|
248
|
+
await window.toolboxAPI.utils.copyToClipboard(markdownC
|
|
249
|
+
const steps = await window.dataverseAPI.executeQuery(
|
|
250
|
+
`sdkmessageprocessingsteps?$select=...&$filter=...&$expand=...`
|
|
251
|
+
);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Notifications
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
await window.toolboxAPI.utils.showNotification({
|
|
258
|
+
title: "Export Successful",
|
|
259
|
+
body: "Exported 15 plugin assembly steps",
|
|
260
|
+
type: "success",
|
|
261
|
+
duration: 3000,
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### File Operations
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Save file
|
|
269
|
+
await window.toolboxAPI.utils.saveFile("plugin_steps.csv", csvContent);
|
|
270
|
+
|
|
271
|
+
// Copy to clipboard
|
|
272
|
+
await window.toolboxAPI.utils.copyToClipboard(content);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Theme Management
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Get current theme
|
|
279
|
+
const theme = await window.toolboxAPI.utils.getCurrentTheme();
|
|
280
|
+
// Returns 'light' or 'dark'
|
|
281
|
+
|
|
282
|
+
// Listen for theme changes
|
|
283
|
+
window.toolboxAPI.onToolboxEvent((event) => {
|
|
284
|
+
if (event === "settings:updated") {
|
|
285
|
+
updateThemeBasedOnSettings();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### EveToolboxAPI\*\*: Provides access to Toolbox API utilities
|
|
291
|
+
|
|
292
|
+
### Services
|
|
293
|
+
|
|
294
|
+
- **loggerService**: Singleton service for centralized logging
|
|
295
|
+
|
|
296
|
+
- Methods: `info()`, `success()`, `warning()`, `error()`
|
|
297
|
+
- Eliminates prop drilling for logging across components
|
|
298
|
+
|
|
299
|
+
- **dataverseService**: Handles all Dataverse API queries
|
|
300
|
+
- Queries system users, teams, security roles, and team memberships
|
|
301
|
+
- Implements paging for large data sets
|
|
302
|
+
- Maps raw API responses to typed models
|
|
303
|
+
|
|
304
|
+
### Components
|
|
305
|
+
|
|
306
|
+
- **Overview**: Main container with state management for filtering, selection, and export
|
|
307
|
+
- **Filter**: Provides all filtering controls (entity type, status, user type, business unit, search)
|
|
308
|
+
- **DataGridView**: Sortable, selectable data grid using Fluent UI DataGrid with single-row selection
|
|
309
|
+
- **SecurityRolesPanel**: Side panel displaying security roles and team memberships
|
|
310
|
+
|
|
311
|
+
### Export Features
|
|
312
|
+
|
|
313
|
+
- **CSV Export**: Creates a matrix format with users as rows and roles/teams as columns
|
|
314
|
+
- Collects all unique roles and teams across filtered users
|
|
315
|
+
- Marks assignments with 'X'
|
|
316
|
+
- Includes user metadata (name, domain, business unit, status)
|
|
317
|
+
- Progress indicator during export
|
|
318
|
+
- **Markdown Export**: Generates hierarchical documentation
|
|
319
|
+
- Section per user with full details
|
|
320
|
+
- Nested lists for security roles and team memberships
|
|
321
|
+
- Includes metadata like managed status, team types, default indicators
|
|
322
|
+
- Copies to clipboard with success notification
|
|
323
|
+
|
|
324
|
+
## Architecture
|
|
325
|
+
|
|
326
|
+
### Custom Hooks
|
|
327
|
+
|
|
328
|
+
- **useConnection**: Manages Dataverse connection state and refresh logic
|
|
329
|
+
- **useToolboxEvents**: Subscribes to PPTB events and handles callbacks
|
|
330
|
+
- \*\*SystemUser
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
{
|
|
334
|
+
systemuserid: string;
|
|
335
|
+
fullname: string;
|
|
336
|
+
domainname: string;
|
|
337
|
+
businessunitid?: {
|
|
338
|
+
businessunitid: string;
|
|
339
|
+
name: string;
|
|
340
|
+
};
|
|
341
|
+
isdisabled: boolean;
|
|
342
|
+
applicationid: string | null; // Populated for application users
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Team
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
{
|
|
350
|
+
teamid: string;
|
|
351
|
+
name: string;
|
|
352
|
+
teamtype: number; // 0 = Owner, 1 = Access
|
|
353
|
+
businessunitid?: {
|
|
354
|
+
businessunitid: string;
|
|
355
|
+
name: string;
|
|
356
|
+
};
|
|
357
|
+
isdefault: boolean;
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### SecurityRole
|
|
362
|
+
|
|
363
|
+
````typescript
|
|
364
|
+
{
|
|
365
|
+
roleid: string;
|
|
366
|
+
name: string;
|
|
367
|
+
businessunitid?: {
|
|
368
|
+
Verify permissions to read system user, team, and security role data
|
|
369
|
+
- Check console logs for API errors
|
|
370
|
+
};
|
|
371
|
+
ismanaged: boolean
|
|
372
|
+
|
|
373
|
+
Full TypeScript coverage with:
|
|
374
|
+
|
|
375
|
+
- Interface definitions for all data models
|
|
376
|
+
- Type-safe API responses
|
|
377
|
+
- Strongly typed component props
|
|
378
|
+
- PPTB API types from `@pptb/types` package
|
|
379
|
+
|
|
380
|
+
## Configuration
|
|
381
|
+
|
|
382
|
+
### Vite Build Configuration
|
|
383
|
+
|
|
384
|
+
The tool uses a custom Vite configuration for PPTB compatibility:
|
|
385
|
+
|
|
386
|
+
- **IIFE format**: Bundles as Immediately Invoked Function Expression for iframe compatibility
|
|
387
|
+
- **Single bundle**: Uses `inlineDynamicImports` to avoid module loading issues with file:// URLs
|
|
388
|
+
- **HTML transformation**: Custom plugin removes `type="module"` and moves scripts to end of body
|
|
389
|
+
- **Chunk size limit**: Set to 1000 kB to accommodate Fluent UI bundle size
|
|
390
|
+
|
|
391
|
+
## Data Models
|
|
392
|
+
|
|
393
|
+
### PluginAssembly
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
{
|
|
397
|
+
pluginassemblyid: string;
|
|
398
|
+
name: string;
|
|
399
|
+
version: string;
|
|
400
|
+
}
|
|
401
|
+
````
|
|
402
|
+
|
|
403
|
+
### PluginAssemblyStep
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
{
|
|
407
|
+
id: string;
|
|
408
|
+
name: string;
|
|
409
|
+
sdkMessage: string;
|
|
410
|
+
mode: string; // Sync/Async
|
|
411
|
+
stage: string; // PreValidation/PreOperation/PostOperation
|
|
412
|
+
rank: number; // Execution order
|
|
413
|
+
eventHandler: string; // Full type name
|
|
414
|
+
filteringattributes: string;
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Troubleshooting
|
|
419
|
+
|
|
420
|
+
### Build Issues
|
|
421
|
+
|
|
422
|
+
If you encounter chunk size warnings:
|
|
423
|
+
|
|
424
|
+
- The tool uses IIFE format which requires a single bundle
|
|
425
|
+
- Chunk size limit is configured in `vite.config.ts`
|
|
426
|
+
- This is expected for Fluent UI components
|
|
427
|
+
|
|
428
|
+
### Connection Issues
|
|
429
|
+
|
|
430
|
+
- Ensure you're connected to a Dataverse environment in PPTB
|
|
431
|
+
- Check the Event Log for connection-related errors
|
|
432
|
+
- Verify permissions to read plugin assembly data
|
|
433
|
+
|
|
434
|
+
### Theme Not Updating
|
|
435
|
+
|
|
436
|
+
- The tool automatically syncs with PPTB theme settings
|
|
437
|
+
- Check console for theme update events
|
|
438
|
+
- Verify PPTB version supports theme API
|
|
439
|
+
|
|
440
|
+
## Contributing
|
|
441
|
+
|
|
442
|
+
Contributions are welcome! Please:
|
|
443
|
+
|
|
444
|
+
1. Fork the repository
|
|
445
|
+
2. Create a feature branch
|
|
446
|
+
3. Make your changes with appropriate TypeScript types
|
|
447
|
+
4. Test the build process
|
|
448
|
+
5. Submit a pull request
|
|
449
|
+
|
|
450
|
+
### GitHub Actions
|
|
451
|
+
|
|
452
|
+
The project includes automated CI/CD workflows:
|
|
453
|
+
|
|
454
|
+
#### CI Workflow (`.github/workflows/ci.yml`)
|
|
455
|
+
|
|
456
|
+
Runs on every push and pull request to `main` and `develop` branches:
|
|
457
|
+
|
|
458
|
+
- **Build and Test**:
|
|
459
|
+
|
|
460
|
+
- Tests on Node.js 18.x and 20.x
|
|
461
|
+
- TypeScript type checking
|
|
462
|
+
- Build verification
|
|
463
|
+
- Uploads build artifacts
|
|
464
|
+
|
|
465
|
+
- **Lint Check**:
|
|
466
|
+
|
|
467
|
+
- Runs ESLint if configured
|
|
468
|
+
- Validates code quality
|
|
469
|
+
|
|
470
|
+
- **Security Audit**:
|
|
471
|
+
|
|
472
|
+
- Checks for npm package vulnerabilities
|
|
473
|
+
- Fails on critical vulnerabilities
|
|
474
|
+
- Warns on high-severity issues
|
|
475
|
+
|
|
476
|
+
- **Package Validation**:
|
|
477
|
+
- Validates package.json structure
|
|
478
|
+
- Creates npm-shrinkwrap.json
|
|
479
|
+
- Verifies all required fields
|
|
480
|
+
|
|
481
|
+
#### Release Workflow (`.github/workflows/release.yml`)
|
|
482
|
+
|
|
483
|
+
Triggered when pushing a version tag (e.g., `v1.0.0`):
|
|
484
|
+
|
|
485
|
+
- Builds the project
|
|
486
|
+
- Creates distribution packages (tar.gz and zip)
|
|
487
|
+
- Creates GitHub release with auto-generated notes
|
|
488
|
+
- Attaches build artifacts to release
|
|
489
|
+
|
|
490
|
+
**To create a release:**
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
# Update version in package.json
|
|
494
|
+
npm version patch # or minor, major
|
|
495
|
+
|
|
496
|
+
# Push with tags
|
|
497
|
+
git push origin main --tags
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## License
|
|
501
|
+
|
|
502
|
+
MIT - See LICENSE file for details
|
|
503
|
+
|
|
504
|
+
## Author
|
|
505
|
+
|
|
506
|
+
Lars Hildebrandt
|