Refactor creation of developers
MODIFY
package-lock.json
package-lock.json
}
},
"keycloak-js": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-8.0.2.tgz",
- "integrity": "sha512-5Jy5rHx0oURTtj2T6LSMLiqyHzDzPr6ZnaY1fwCBlMsiMx7RVsIBeysYZ5yy29ojH9IMzNJzlZvuLKbEOCmgDQ==",
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-9.0.3.tgz",
+ "integrity": "sha512-c8FFPa8YiJmPbJEMZ/mIrHflBR6FIFUm5xTWtIDzlrnoeF4u0wDmTBfo1u71rWIL1HanLvg3T+9AgR1NqfmGbA==",
"requires": {
"base64-js": "1.3.1",
"js-sha256": "0.9.0"

MODIFY
package.json
package.json
"@angular/router": "~9.1.1",
"angular2-toaster": "^8.0.0",
"keycloak-admin": "^1.13.0",
- "keycloak-angular": "^7.1.0",
- "keycloak-js": "^8.0.1",
+ "keycloak-angular": "^7.2.0",
+ "keycloak-js": "^9.0.3",
"ngx-logger": "^4.0.7",
"rxjs": "^6.5.5",
"swagger-ui-dist": "^3.24.3",

MODIFY
src/app/components/admin/create-developer/client-mapping.component.scss
src/app/components/admin/create-developer/client-mapping.component.scss
.client-container {
- width: 250px;
+ width: calc(50% - 10px);
max-width: 100%;
- margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
+.client-container:first-of-type {
+ margin-right: 20px;
+}
+
.container-header {
padding: 8px;
color: #ffffff;

MODIFY
src/app/components/admin/create-developer/create-developer.component.html
src/app/components/admin/create-developer/create-developer.component.html
<h1>Create Developer</h1>
<div class="wrapper">
- <mat-horizontal-stepper linear #stepper>
- <mat-step [stepControl]="userFormGroup">
+
<form id="userForm" [formGroup]="userFormGroup">
- <ng-template matStepLabel>Create User</ng-template>
+ <h3>Select User</h3>
<mat-form-field class="input-form-field">
- <input matInput placeholder="Username" formControlName="username" [matAutocomplete]="auto"
+ <input matInput placeholder="Select or search for an existing user" formControlName="username" [matAutocomplete]="auto"
(input)="usernameKeyUp.next($event.target.value)" required>
</mat-form-field>
<mat-autocomplete #auto="matAutocomplete">
- <mat-option *ngFor="let user of filteredKeycloakUsers" [value]="user.username">{{user.username}}</mat-option>
+ <mat-option *ngFor="let user of filteredKeycloakUsers" [value]="user.username">{{user.username}} ({{user.email}})</mat-option>
</mat-autocomplete>
- <span [hidden]="!this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value)">
- <mat-form-field class="input-form-field">
- <!-- use attr.disabled here because of formGroup: https://angular.io/guide/template-syntax -->
- <input id="passwordField" matInput placeholder="Password" type="password" formControlName="password"
- [required]="this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value)"
- [attr.disabled]="!this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value) ? 'disabled' : null">
- </mat-form-field>
- <mat-form-field class="input-form-field">
- <input matInput placeholder="First name" formControlName="firstname"
- [required]="this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value)"
- [attr.disabled]="!this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value) ? 'disabled' : null">
- </mat-form-field>
- <mat-form-field class="input-form-field">
- <input matInput placeholder="Last name" formControlName="lastname"
- [required]="this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value)"
- [attr.disabled]="!this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value) ? 'disabled' : null">
- </mat-form-field>
- <mat-form-field class="input-form-field">
- <input matInput placeholder="Email" formControlName="email"
- [required]="this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value)"
- [attr.disabled]="!this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value) ? 'disabled' : null">
- </mat-form-field>
- </span>
- <mat-card class="scheer-box-warning" *ngIf="!this.checkDeveloperNotExists(this.userFormGroup.get('username').value)">
- <mat-icon>warning</mat-icon>
- <div class="scheer-box-content">Developer already exists.</div>
- </mat-card>
- <mat-card class="scheer-box-warning" *ngIf="this.checkEmailAlreadyUsed(this.userFormGroup.get('email').value)">
- <mat-icon>warning</mat-icon>
- <div class="scheer-box-content">Email already used.</div>
- </mat-card>
- <mat-card
- class="scheer-box-info"
- *ngIf="this.checkDeveloperNotExists(this.userFormGroup.get('username').value)
- && !this.isPasswordRequiredForInsert(this.userFormGroup.get('username').value)">
- <mat-icon>info</mat-icon>
- <div class="scheer-box-content">You create a Developer for an existing API Management user.</div>
- </mat-card>
</form>
- <div>
- <button matStepperNext
- [disabled]="!this.checkDeveloperNotExists(this.userFormGroup.get('username').value) || this.checkEmailAlreadyUsed(this.userFormGroup.get('email').value) || !userFormGroup.valid">
- Next
- </button>
- <button routerLink="/admin">Cancel</button>
- </div>
- </mat-step>
- <mat-step>
- <ng-template matStepLabel>Create Client Mapping</ng-template>
+
+ <h3>Create Client-Mapping</h3>
<app-client-mapping #clientmapping [assignedClients]="[]"></app-client-mapping>
<p>
<button
[disabled]="!this.checkDeveloperNotExists(this.userFormGroup.get('username').value) || !userFormGroup.valid"
(click)="insertDeveloper()">Save
</button>
- <button matStepperPrevious>Back</button>
<button routerLink="/admin">Cancel</button>
</p>
- </mat-step>
- </mat-horizontal-stepper>
+
+ <mat-card class="scheer-box-warning" *ngIf="!this.checkDeveloperNotExists(this.userFormGroup.get('username').value)">
+ <mat-icon>warning</mat-icon>
+ <div class="scheer-box-content">Developer already exists.</div>
+ </mat-card>
+
</div>

MODIFY
src/app/components/admin/create-developer/create-developer.component.scss
src/app/components/admin/create-developer/create-developer.component.scss
.input-form-field {
- width: 150px;
- margin-left: 20px;
+ width: 100%;
}
.wrapper {
- min-width: 150px;
- max-width: 700px;
+ max-width: 50%;
width: 100%;
}
#userForm {
- min-height: 130px;
+ //No styling at the moment
}
// stepper styling

MODIFY
src/app/components/admin/create-developer/create-developer.component.ts
src/app/components/admin/create-developer/create-developer.component.ts
import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
-import {FormControl, FormGroup, Validators} from '@angular/forms';
+import {FormControl, FormGroup} from '@angular/forms';
import {DeveloperImpl} from '../../../type-definitions/developerImpl';
import {ClientMappingComponent} from './client-mapping.component';
import {ClientMappingImpl} from '../../../type-definitions/client-mapping-impl';
import {Router} from '@angular/router';
-import {KeycloakUserImpl} from '../edit-developer/keycloak-user-impl';
import {AdminService} from '../services/admin.service';
import {DeveloperDataCacheService} from '../services/developer-data-cache.service';
import {forkJoin, Subject} from 'rxjs';
-import {debounceTime, map, mergeMap} from 'rxjs/operators';
-import {Toast, ToasterService} from 'angular2-toaster';
+import {debounceTime, map} from 'rxjs/operators';
+import {ToasterService} from 'angular2-toaster';
import UserRepresentation from 'keycloak-admin/lib/defs/userRepresentation';
import {SpinnerService} from '../../../services/spinner.service';
@@ -20,24 +19,16 @@ import {SpinnerService} from '../../../services/spinner.service';
})
export class CreateDeveloperComponent implements OnInit, OnDestroy {
- // pattern allows only strings without slashes and square brackets (taken from keycloak)
- private usernamePattern = Validators.pattern('^[^\\<\\>\\\\\\/]*$');
-
public userFormGroup = new FormGroup({
- email: new FormControl('', [Validators.email]),
- firstname: new FormControl(''),
- lastname: new FormControl(''),
- password: new FormControl(''),
- username: new FormControl('', [this.usernamePattern])
+ username: new FormControl('')
});
@ViewChild('clientmapping', {static: false}) clientMapping: ClientMappingComponent;
public usernameKeyUp = new Subject<string>();
- private userNameInputSubscription;
-
public keycloakUsers: Array<UserRepresentation>;
public filteredKeycloakUsers: Array<UserRepresentation>;
+ private userNameInputSubscription;
constructor(private adminService: AdminService,
private router: Router,
@@ -79,41 +70,28 @@ export class CreateDeveloperComponent implements OnInit, OnDestroy {
this.userNameInputSubscription.unsubscribe();
}
- /**
- * Filter the user auto complete list
- * @param usernameInput the inputed username
- */
- private filterKeycloakUser(usernameInput: string) {
- this.filteredKeycloakUsers = this.keycloakUsers
- .filter((u) => this.checkDeveloperNotExists(u.username) && u.username.toLowerCase().includes(usernameInput.toLowerCase()));
- }
-
/**
* Insert a developer
*/
insertDeveloper() {
this.loadingSpinnerService.startWaiting();
+ const keycloakUser: UserRepresentation = this.filteredKeycloakUsers
+ .find(user => user.username === this.userFormGroup.get('username').value);
+
const developerToCreate = new DeveloperImpl();
- developerToCreate.name = this.userFormGroup.get('username').value;
+ developerToCreate.id = keycloakUser.username;
developerToCreate.clients = [];
this.clientMapping.assignedClients.forEach(client => {
developerToCreate.clients.push(new ClientMappingImpl(client.clientId, client.organizationId));
});
- const keycloakUserToCreate = new KeycloakUserImpl();
- keycloakUserToCreate.username = this.userFormGroup.get('username').value;
- keycloakUserToCreate.email = this.userFormGroup.get('email').value;
- keycloakUserToCreate.firstName = this.userFormGroup.get('firstname').value;
- keycloakUserToCreate.lastName = this.userFormGroup.get('lastname').value;
- keycloakUserToCreate.password = this.userFormGroup.get('password').value;
-
- this.adminService.createNewDeveloper(developerToCreate, keycloakUserToCreate)
+ this.adminService.createNewDeveloper(developerToCreate, keycloakUser.id)
.subscribe(createdDeveloper => {
this.userFormGroup.reset();
this.clientMapping.reset();
this.developerDataCache.developers.push(createdDeveloper);
- console.log('pushed developer to cache', createdDeveloper);
+ console.log('Pushed developer to cache', createdDeveloper);
this.loadingSpinnerService.stopWaiting();
this.router.navigate(['/admin']);
}, error => {
@@ -141,22 +119,11 @@ export class CreateDeveloperComponent implements OnInit, OnDestroy {
}
/**
- * Check if email adress is used
- * @param email the email address
- */
- checkEmailAlreadyUsed(email: string) {
- // check if the keycloak user does not exists
- return this.keycloakUsers
- && this.keycloakUsers.find((u) => u.email && u.email.toLowerCase() === email.toLowerCase()) !== undefined;
- }
-
- /**
- * Check if the a password is required for an insert
- * @param username the keycloak username
+ * Filter the user auto complete list
+ * @param usernameInput the entered username
*/
- isPasswordRequiredForInsert(username: string) {
- // check if the keycloak user does not exists
- return this.keycloakUsers
- && this.keycloakUsers.find((u) => u.username.toLowerCase() === username.toLowerCase()) === undefined;
+ private filterKeycloakUser(usernameInput: string) {
+ this.filteredKeycloakUsers = this.keycloakUsers
+ .filter((u) => this.checkDeveloperNotExists(u.username) && u.username.toLowerCase().includes(usernameInput.toLowerCase()));
}
}

MODIFY
src/app/components/admin/services/admin.service.ts
src/app/components/admin/services/admin.service.ts
import {Inject, Injectable} from '@angular/core';
-import {forkJoin, Observable, of, Subject} from 'rxjs';
-import {catchError, map, mergeMap, share} from 'rxjs/operators';
-import UserRepresentation from 'keycloak-admin/lib/defs/userRepresentation';
-import {ClientSearchResult, Developer, KeycloakUser} from '../../../services/api-data.service';
+import {Observable, of} from 'rxjs';
+import {catchError, map, mergeMap} from 'rxjs/operators';
+import {ClientSearchResult, Developer} from '../../../services/api-data.service';
import {HttpClient} from '@angular/common/http';
import {KeycloakInteractionService} from './keycloak-interaction.service';
@@ -30,14 +29,6 @@ export class AdminService {
return this.http.get(url) as Observable<Array<Developer>>;
}
- /**
- * Get developer by name
- * @param name the developer name
- */
- private getDeveloperByName(name: string) {
- return this.getAllDevelopers().pipe(map(developers => developers.find(d => d.name.toLowerCase() === name.toLowerCase())));
- }
-
/**
* Get developer by developer id
* @param developerId The developer id
@@ -57,40 +48,26 @@ export class AdminService {
/**
* Create new developer
* @param developer the developer to create
+ * @param keycloakUserId the id of the keycloak user
*/
- public createNewDeveloper(developer: Developer, keycloakUserToInsert: KeycloakUser) {
- // observer to insert keycloak user
- const insertToKeycloak = this.keycloak.findExistingOrCreateUser(keycloakUserToInsert);
+ public createNewDeveloper(developer: Developer, keycloakUserId: string) {
// observer insert developer to API-Mgmt
// response has developer object with developer id
const url = this.apiMgmtUiRestUrl + '/developers';
const insertToApiMgmt = this.http.post(url, developer) as Observable<Developer>;
// observer to add keycloak user to developer portal group
- const addDevPortalGroupToUser = (insertedKeycloakUser) => this.keycloak.addDevPortalGroupToUser(insertedKeycloakUser.id);
- // observer to add client role to user
- const addClientRoleToUser = (insertedKeycloakUser, insertedDeveloper) => {
- const clientRoleDescription = 'role for user: ' + insertedDeveloper.name;
- return this.keycloak.addClientRoleToUser(insertedKeycloakUser.id, insertedDeveloper.id, clientRoleDescription);
- };
+ const addDevPortalGroupToUser = this.keycloak.addDevPortalGroupToUser(keycloakUserId);
- // 1. insert user into keycloak
- return insertToKeycloak.pipe(mergeMap(insertedKeycloakUser => {
// 2. insert developer to API-Mgmt
return insertToApiMgmt.pipe(mergeMap(insertedDeveloper => {
// 3. add devPortal group and add client role to keycloak user
- return forkJoin([addDevPortalGroupToUser(insertedKeycloakUser), addClientRoleToUser(insertedKeycloakUser, insertedDeveloper)])
- .pipe(map(() => insertedDeveloper),
- catchError((err, caught) => {
- // rollback developer if keycloak settings cannot be done
- this.deleteDeveloperFromApiMgmt(insertedDeveloper.id).subscribe();
- throw err;
- }));
+ return forkJoin([addDevPortalGroupToUser])
+ .pipe(map(() => insertedDeveloper));
}), catchError((err, caught) => {
// rollback keycloak user if developer cannot created at API-Mgmt
- this.keycloak.deleteUser(keycloakUserToInsert.username).subscribe();
+ // this.keycloak.deleteUser(keycloakUserToInsert.username).subscribe();
throw err;
}));
- }));
}
/**
@@ -105,25 +82,13 @@ export class AdminService {
});
}
- /**
- * Delete developer from API Mgmt
- * @param developerId the developer id
- */
- private deleteDeveloperFromApiMgmt(developerId: string) {
- const url = this.apiMgmtUiRestUrl + '/developers/' + developerId;
- return this.http.delete(url);
- }
-
/**
* Delete a developer
* @param developer the developer to update
*/
public deleteDeveloper(developer: Developer) {
- return forkJoin([
- this.deleteDeveloperFromApiMgmt(developer.id),
- this.keycloak.deleteClientRole(developer.id),
- this.keycloak.deleteUser(developer.name)
- ]);
+ const url = this.apiMgmtUiRestUrl + '/developers/' + developer.id;
+ return this.http.delete(url);
}
/**

MODIFY
src/app/components/admin/services/keycloak-interaction.service.ts
src/app/components/admin/services/keycloak-interaction.service.ts
@@ -16,7 +16,7 @@ export class KeycloakInteractionService {
private keycloakGroupUsers = 'API-Mgmt-Devportal-Users';
private keycloakDevPortalClientId = 'devportal';
- private keycloakDevPortalUserRole = 'devportaluser';
+ private keycloakDevPortalUserRole = 'devportaluser'; // TODO REMOVE
/**
* Constructor of Keycloak Interaction Service
@@ -53,6 +53,7 @@ export class KeycloakInteractionService {
.pipe(share()); // fire only once and cache the result for other subscribers
}
+ // TODO REMOVE
/**
* Get the devportaluser user role
*/
@@ -72,9 +73,10 @@ export class KeycloakInteractionService {
/**
* Get all keycloak users
+ * Max value: https://www.keycloak.org/docs-api/9.0/rest-api/index.html#_users_resource
*/
public getAllUsers() {
- return from(this.kcAdminClient.users.find());
+ return from(this.kcAdminClient.users.find({max: (Math.pow(2, 31) - 1)}));
}
/**
@@ -95,11 +97,11 @@ export class KeycloakInteractionService {
lastName: userToCreate.lastName,
email: userToCreate.email,
username: userToCreate.username,
- enabled: true,
- attributes: {generatedFromDevPortal: ['true']}
+ enabled: true
}));
}
+ // TODO REMOVE
/**
* Delete user by id
* @param userId the user id
@@ -110,6 +112,7 @@ export class KeycloakInteractionService {
}));
}
+ // TODO REMOVE
/**
* Set user password
* @param userId the user id
@@ -126,6 +129,7 @@ export class KeycloakInteractionService {
}));
}
+ // TODO REMOVE
/**
* Create client role
* @param clientRoleName the client role name
@@ -178,6 +182,7 @@ export class KeycloakInteractionService {
}));
}
+ // TODO REMOVE
/**
* Remove Role Mapping from user
* @param userUUID user UUID
@@ -217,6 +222,7 @@ export class KeycloakInteractionService {
})));
}
+ // TODO REMOVE
/**
* Remove User from API-Mgmt-Devportal-Users group
* @param userId the user id
@@ -243,51 +249,54 @@ export class KeycloakInteractionService {
);
}
+ // TODO REMOVE
/**
* Find existing or create new user
* @param user the user
*/
- public findExistingOrCreateUser(user: KeycloakUser): Observable<UserRepresentation> {
- return this.findUser(user.username).pipe(mergeMap(foundUsers => {
- let observer = null;
- if (foundUsers.length === 0) {
- observer = this.createUser(user).pipe(mergeMap((insertedUser => {
- return this.setUserPassword(insertedUser.id, user.password).pipe(mergeMap(() => of(insertedUser)));
- })));
- } else {
- observer = of(foundUsers[0]);
- }
- return observer;
- }));
- }
+ // public findExistingOrCreateUser(user: KeycloakUser): Observable<UserRepresentation> {
+ // return this.findUser(user.username).pipe(mergeMap(foundUsers => {
+ // let observer = null;
+ // if (foundUsers.length === 0) {
+ // observer = this.createUser(user).pipe(mergeMap((insertedUser => {
+ // return this.setUserPassword(insertedUser.id, user.password).pipe(mergeMap(() => of(insertedUser)));
+ // })));
+ // } else {
+ // observer = of(foundUsers[0]);
+ // }
+ // return observer;
+ // }));
+ // }
+ // TODO REMOVE
/**
* Determines if the user was generated from devportal admin
* @param user Keycloak user
*/
- private isUserGeneratedFromDevPortal(user: UserRepresentation): boolean {
- return user.attributes
- && user.attributes.generatedFromDevPortal
- && user.attributes.generatedFromDevPortal.length > 0
- && user.attributes.generatedFromDevPortal[0] === 'true';
- }
+ // private isUserGeneratedFromDevPortal(user: UserRepresentation): boolean {
+ // return user.attributes
+ // && user.attributes.generatedFromDevPortal
+ // && user.attributes.generatedFromDevPortal.length > 0
+ // && user.attributes.generatedFromDevPortal[0] === 'true';
+ // }
+ // TODO REMOVE
/**
* Delete dev portal user if generated from dev portal if not remove only devportaluser role from user
* @param username Username of Developer
*/
- public deleteUser(username: string): Observable<void> {
- return this.findUser(username).pipe(mergeMap(users => {
- if (users.length > 0) {
- const userToDelete = users[0];
- // delete user only if he was generated from dev portal
- if (this.isUserGeneratedFromDevPortal(userToDelete)) {
- return this.deleteUserById(userToDelete.id);
- } else {
- // remove only devportaluser role from user if he was not generated from dev portal
- return this.removeDevPortalGroupFromUser(userToDelete.id);
- }
- }
- }));
- }
+ // public deleteUser(username: string): Observable<void> {
+ // return this.findUser(username).pipe(mergeMap(users => {
+ // if (users.length > 0) {
+ // const userToDelete = users[0];
+ // // delete user only if he was generated from dev portal
+ // if (this.isUserGeneratedFromDevPortal(userToDelete)) {
+ // return this.deleteUserById(userToDelete.id);
+ // } else {
+ // // remove only devportaluser role from user if he was not generated from dev portal
+ // return this.removeDevPortalGroupFromUser(userToDelete.id);
+ // }
+ // }
+ // }));
+ // }
}

MODIFY
src/app/components/developer/developer.component.ts
src/app/components/developer/developer.component.ts
-import {Component, OnInit} from '@angular/core';
+import {Component} from '@angular/core';
import {KeycloakService} from 'keycloak-angular';
import {TokenService} from '../../services/token.service';
@@ -10,8 +10,6 @@ import {TokenService} from '../../services/token.service';
export class DeveloperComponent {
- public developers: Array<string> = [];
-
/**
* load the keycloak roles from keycloak service
* @param keycloak the keycloak service

GW-335: Refactor creation of developers