Insert and Delete Developers with Keycloak user creation
MODIFY
src/app/admin/admin.component.ts
src/app/admin/admin.component.ts
@@ -5,11 +5,8 @@ import { Component, OnInit } from '@angular/core';
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.scss']
})
-export class AdminComponent implements OnInit {
+export class AdminComponent {
constructor() { }
- ngOnInit() {
- }
-
}

ADD
src/app/admin/admin.service.spec.ts
+import { TestBed } from '@angular/core/testing';
+
+import { AdminService } from './admin.service';
+
+describe('AdminService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: AdminService = TestBed.get(AdminService);
+ expect(service).toBeTruthy();
+ });
+});

ADD
src/app/admin/admin.service.ts
+import { Inject, Injectable } from '@angular/core';
+import { forkJoin, Observable } from 'rxjs';
+import { map, mergeMap } from 'rxjs/operators';
+import UserRepresentation from 'keycloak-admin/lib/defs/userRepresentation';
+import { ClientSearchResult, Developer, KeycloakUser } from '../api-data.service';
+import { HttpClient } from '@angular/common/http';
+import { KeycloakInteractionService } from './keycloak-interaction.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AdminService {
+
+ /**
+ * Constructor of Admin Service
+ * @param http Http Client
+ * @param keycloak Keycloak Interaction Service
+ * @param apimanUiRestUrl Apiman UI REST Url
+ */
+ constructor(private http: HttpClient,
+ private keycloak: KeycloakInteractionService, @Inject('APIMAN_UI_REST_URL')
+ private apimanUiRestUrl: string) {}
+
+ /**
+ * Get all available developers
+ */
+ public getAllDevelopers() {
+ const url = this.apimanUiRestUrl + '/developers';
+ return this.http.get(url) as Observable<Array<Developer>>;
+ }
+
+ /**
+ * Get developer by developer id
+ * @param developerId The developer id
+ */
+ public getDeveloper(developerId: string) {
+ const url = this.apimanUiRestUrl + '/developers/' + developerId;
+ return this.http.get(url) as Observable<Developer>;
+ }
+
+ public isPasswordRequired(username) {
+ return this.keycloak.searchUser(username)
+ .pipe(map(users => users.findIndex((user => user.username === username)) === -1));
+ }
+
+ /**
+ * Create new developer
+ * @param developer the developer to create
+ */
+ public createNewDeveloper(developer: Developer, keycloakUserToInsert: KeycloakUser) {
+ const url = this.apimanUiRestUrl + '/developers';
+
+ // 1. insert developer to API-Mgmt
+ // response has developer object with developer id
+ const insertRequests = (this.http.post(url, developer) as Observable<Developer>)
+ .pipe(mergeMap(developerInserted => {
+ // 2. insert user into keycloak
+ return forkJoin(this.keycloak.findExistingOrCreateUser(keycloakUserToInsert)
+ .pipe(mergeMap((insertedKeycloakUser) =>
+ // 3. add devPortal role and add client role to keycloak user
+ forkJoin(this.keycloak.addDevPortalRoleToUser(insertedKeycloakUser),
+ this.keycloak.addClientRoleToUser(insertedKeycloakUser, developerInserted.id))
+ )))
+ .pipe(map(() => developerInserted));
+ }));
+ return insertRequests;
+ }
+
+ /**
+ * Update a developer
+ * @param developer the developer to update
+ */
+ public updateDeveloper(developer: Developer) {
+ const url = this.apimanUiRestUrl + '/developers/' + developer.id;
+ return this.http.put(url, {
+ name: developer.name,
+ clients: developer.clients
+ });
+ }
+
+ /**
+ * Delete a developer
+ * @param developer the developer to update
+ */
+ public deleteDeveloper(developer: Developer) {
+ const url = this.apimanUiRestUrl + '/developers/' + developer.id;
+ const deleteDeveloperFromApiman = this.http.delete(url);
+ return forkJoin(
+ deleteDeveloperFromApiman,
+ this.keycloak.deleteClientRole(developer.id),
+ this.keycloak.deleteUser(developer.name)
+ );
+ }
+
+ /**
+ * Get all available clients
+ */
+ public getAllClients() {
+ const url = this.apimanUiRestUrl + '/search/clients';
+ const searchQuery = {
+ filters: [{
+ name: 'name',
+ value: '*',
+ operator: 'like'
+ }],
+
+ paging: {
+ page: '1',
+ pageSize: '10000'
+ }
+ };
+ return (this.http.post(url, searchQuery) as Observable<ClientSearchResult>)
+ .pipe(mergeMap( searchResult => searchResult.beans));
+ }
+}

MODIFY
src/app/admin/create-developer/client-mapping.component.ts
src/app/admin/create-developer/client-mapping.component.ts
import { Component, OnInit, Input, Output } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { ApiDataService, ClientBean, ClientMapping } from '../../api-data.service';
-import {map, mergeAll, mergeMap} from 'rxjs/operators';
+import { map } from 'rxjs/operators';
+import { AdminService } from '../admin.service';
@Component({
selector: 'app-client-mapping',
@@ -10,7 +11,7 @@ import {map, mergeAll, mergeMap} from 'rxjs/operators';
})
export class ClientMappingComponent implements OnInit {
- constructor(private apiDataService: ApiDataService) { }
+ constructor(private adminService: AdminService) { }
availableClients: Array<ClientMapping> = [];
@@ -22,7 +23,7 @@ export class ClientMappingComponent implements OnInit {
loadClients() {
const loadedAvailableClients = [];
- this.apiDataService.getAllClients().pipe(map(client => {
+ this.adminService.getAllClients().pipe(map(client => {
if (this.assignedClients && !this.assignedClients.find((c => c.organizationId === client.organizationId && c.clientId === client.id))) {
loadedAvailableClients.push({
clientId: client.id,

MODIFY
src/app/admin/create-developer/create-developer.component.html
src/app/admin/create-developer/create-developer.component.html
<div class="wrapper">
<form id="userForm" [formGroup]="userFormGroup">
<mat-form-field class="input-form-field">
- <input matInput placeholder="Username" formControlName="username" (input)="checkPasswordRequired($event.target.value)" required>
+ <input matInput placeholder="Username" formControlName="username" (input)="usernameKeyUp.next($event.target.value)" required>
</mat-form-field>
- <span [hidden]="isPasswordRequiredForInsert">(You create a Developer for an existing API-Mgmt user.)</span>
+ <span [hidden]="this.checkDeveloperNotExists(this.userFormGroup.get('username').value)">(Developer already exists.)</span>
+ <span [hidden]="!this.checkDeveloperNotExists(this.userFormGroup.get('username').value) || isPasswordRequiredForInsert">(You create a Developer for an existing API-Mgmt user.)</span>
<span [hidden]="!isPasswordRequiredForInsert">
<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]="isPasswordRequiredForInsert" [attr.disabled]="!isPasswordRequiredForInsert ? 'disabled' : null">
</mat-form-field>
<mat-form-field class="input-form-field">
- <input matInput placeholder="First name" formControlName="firstname">
+ <input matInput placeholder="First name" formControlName="firstname" [required]="isPasswordRequiredForInsert" [attr.disabled]="!isPasswordRequiredForInsert ? 'disabled' : null">
</mat-form-field>
<mat-form-field class="input-form-field">
- <input matInput placeholder="Last name" formControlName="lastname">
+ <input matInput placeholder="Last name" formControlName="lastname" [required]="isPasswordRequiredForInsert" [attr.disabled]="!isPasswordRequiredForInsert ? 'disabled' : null">
</mat-form-field>
<mat-form-field class="input-form-field">
- <input matInput placeholder="Email" formControlName="email">
+ <input matInput placeholder="Email" formControlName="email" [required]="isPasswordRequiredForInsert" [attr.disabled]="!isPasswordRequiredForInsert ? 'disabled' : null">
</mat-form-field>
</span>
</form>
<br/>
<app-client-mapping #clientmapping></app-client-mapping>
<p>
- <button [disabled]="!userFormGroup.valid || clientMapping.assignedClients.length === 0" (click)="insertDeveloper()">Save</button>
+ <button [disabled]="!this.checkDeveloperNotExists(this.userFormGroup.get('username').value) || !userFormGroup.valid || clientMapping.assignedClients.length === 0" (click)="insertDeveloper()">Save</button>
<button routerLink="/admin">Cancel</button>
</p>
</div>

MODIFY
src/app/admin/create-developer/create-developer.component.ts
src/app/admin/create-developer/create-developer.component.ts
-import {Component, Input, OnInit, ViewChild} from '@angular/core';
+import { Component, Input, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
-import {ApiDataService, Developer} from '../../api-data.service';
import { DeveloperImpl } from '../../../developerImpl';
import { DeveloperListComponent } from '../developer-list.component';
import { ClientMappingComponent } from './client-mapping.component';
import { ClientMappingImpl } from '../../../client-mapping-impl';
import { Router } from '@angular/router';
import { KeycloakUserImpl } from '../keycloak-user-impl';
+import { AdminService } from '../admin.service';
+import { DeveloperDataCacheService } from '../developer-data-cache.service';
+import { Subject } from 'rxjs';
+import { debounceTime, map, mergeMap } from 'rxjs/operators';
@Component({
selector: 'app-create-developer',
@@ -30,7 +33,23 @@ export class CreateDeveloperComponent {
@ViewChild('clientmapping', {static: false}) clientMapping: ClientMappingComponent;
- constructor(private apiDataService: ApiDataService, private router: Router) { }
+ public usernameKeyUp = new Subject<KeyboardEvent>();
+ private userNameInputSubscription;
+
+ constructor(private adminService: AdminService, private router: Router, private developerDataCache: DeveloperDataCacheService) { }
+
+ ngOnInit(): void {
+ this.userNameInputSubscription = this.usernameKeyUp
+ .pipe(
+ debounceTime(300),
+ mergeMap(username => this.adminService.isPasswordRequired(username)
+ .pipe(map(isRequired => this.isPasswordRequiredForInsert = isRequired))
+ )).subscribe();
+ }
+
+ ngOnDestroy(): void {
+ this.userNameInputSubscription.unsubscribe();
+ }
insertDeveloper() {
const developerToCreate = new DeveloperImpl();
@@ -47,15 +66,18 @@ export class CreateDeveloperComponent {
keycloakUserToCreate.lastName = this.userFormGroup.get('lastname').value;
keycloakUserToCreate.password = this.userFormGroup.get('password').value;
- this.apiDataService.createNewDeveloper(developerToCreate, keycloakUserToCreate)
+ this.adminService.createNewDeveloper(developerToCreate, keycloakUserToCreate)
.subscribe(createdDeveloper => {
this.userFormGroup.reset();
this.clientMapping.reset();
+ this.developerDataCache.developers.push(createdDeveloper);
this.router.navigate(['/admin']);
});
}
- checkPasswordRequired(username) {
- return this.apiDataService.isPasswordRequired(username).then(isRequired => this.isPasswordRequiredForInsert = isRequired);
+ checkDeveloperNotExists(userName: string) {
+ return this.developerDataCache.developers
+ && this.developerDataCache.developers.find((d) => d.name.toLowerCase() === userName.toLowerCase()) === undefined;
}
+
}

ADD
src/app/admin/developer-data-cache.service.spec.ts
+import { TestBed } from '@angular/core/testing';
+
+import { DeveloperDataCacheService } from './developer-data-cache.service';
+
+describe('DeveloperDataCacheService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: DeveloperDataCacheService = TestBed.get(DeveloperDataCacheService);
+ expect(service).toBeTruthy();
+ });
+});

ADD
src/app/admin/developer-data-cache.service.ts
+import { Injectable } from '@angular/core';
+import { Developer } from '../api-data.service';
+import { AdminService } from './admin.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class DeveloperDataCacheService {
+
+ public developers: Array<Developer>;
+
+ constructor(private adminService: AdminService) {
+ if (!this.developers) {
+ this.adminService.getAllDevelopers().subscribe((developers) => {
+ // set data to cache
+ this.developers = developers;
+ });
+ }
+ }
+}

MODIFY
src/app/admin/developer-list.component.ts
src/app/admin/developer-list.component.ts
import { Component, OnInit } from '@angular/core';
-import {ApiDataService, ClientMapping, Developer} from '../api-data.service';
-import {mergeAll, mergeMap} from 'rxjs/operators';
+import { AdminService } from './admin.service';
+import { Developer } from '../api-data.service';
+import { DeveloperDataCacheService } from './developer-data-cache.service';
@Component({
selector: 'app-developer-list',
@@ -11,22 +12,26 @@ export class DeveloperListComponent implements OnInit {
developers: Array<Developer> = [];
- constructor(private apiDataService: ApiDataService) { }
+ constructor(private adminService: AdminService, private developerDataCache: DeveloperDataCacheService) { }
ngOnInit() {
this.load();
}
public load() {
- console.log('start loading developers');
- this.developers = [];
- this.apiDataService.getAllDevelopers().subscribe((developers) => {
- console.log('Developer', developers);
- this.developers = developers;
+ // set data from cache
+ this.developers = this.developerDataCache.developers;
+ if (!this.developers) {
+ this.adminService.getAllDevelopers().subscribe((developers) => {
+ // set data to cache
+ this.developerDataCache.developers = developers;
+ // set data from cache
+ this.developers = this.developerDataCache.developers;
});
}
+ }
deleteDeveloper(developer: Developer) {
- this.apiDataService.deleteDeveloper(developer).subscribe(response => this.developers.splice(this.developers.indexOf(developer), 1));
+ this.adminService.deleteDeveloper(developer).subscribe(response => this.developers.splice(this.developers.indexOf(developer), 1));
}
}

MODIFY
src/app/admin/edit-developer/edit-developer.component.ts
src/app/admin/edit-developer/edit-developer.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
-import {ApiDataService, Developer} from '../../api-data.service';
+import { Developer } from '../../api-data.service';
import { ClientMappingComponent } from '../create-developer/client-mapping.component';
-import {timeInterval} from 'rxjs/operators';
+import { AdminService } from '../admin.service';
+import { DeveloperDataCacheService } from '../developer-data-cache.service';
@Component({
selector: 'app-edit-developer',
@@ -11,17 +12,17 @@ import {timeInterval} from 'rxjs/operators';
})
export class EditDeveloperComponent implements OnInit {
- private developerId;
+ public developerId;
private developer: Developer;
public assignedClients;
@ViewChild('clientmapping', {static: false}) clientMapping: ClientMappingComponent;
- constructor(private apiDataService: ApiDataService, private route: ActivatedRoute, private router: Router) { }
+ constructor(private adminService: AdminService, private route: ActivatedRoute, private router: Router, private developerDataCache: DeveloperDataCacheService) { }
ngOnInit() {
this.developerId = this.route.snapshot.paramMap.get('developerId');
- this.apiDataService.getDeveloper(this.developerId).subscribe(developer => {
+ this.adminService.getDeveloper(this.developerId).subscribe(developer => {
this.developer = developer;
this.assignedClients = developer.clients;
this.clientMapping.loadClients();
@@ -30,9 +31,9 @@ export class EditDeveloperComponent implements OnInit {
updateDeveloper() {
if (this.developer && this.clientMapping.assignedClients.length > 0) {
- return this.apiDataService.updateDeveloper(this.developer).subscribe(() => {
- console.log('Update Developer done');
- setTimeout(() => this.router.navigate(['/admin']), 500);
+ return this.adminService.updateDeveloper(this.developer).subscribe(() => {
+ this.developerDataCache.developers.splice(this.developerDataCache.developers.findIndex((d) => d.id === this.developer.id), 1, this.developer);
+ this.router.navigate(['/admin']);
});
}
}

ADD
src/app/admin/keycloak-interaction.service.spec.ts
+import { TestBed } from '@angular/core/testing';
+
+import { KeycloakInteractionService } from './keycloak-interaction.service';
+
+describe('KeycloakInteractionService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: KeycloakInteractionService = TestBed.get(KeycloakInteractionService);
+ expect(service).toBeTruthy();
+ });
+});

ADD
src/app/admin/keycloak-interaction.service.ts
+import { Inject, Injectable } from '@angular/core';
+import { KeycloakService } from 'keycloak-angular';
+import KcAdminClient from 'keycloak-admin';
+import { from, Observable, of } from 'rxjs';
+import { map, mergeAll, mergeMap, share, single } from 'rxjs/operators';
+import UserRepresentation from 'keycloak-admin/lib/defs/userRepresentation';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class KeycloakInteractionService {
+
+ private kcAdminClient: KcAdminClient;
+
+ /**
+ * Constructor of Keycloak Interaction Service
+ * @param keycloak the keycloak service
+ * @param apimanKeycloakRestUrl the apiman keycloak rest url
+ */
+ constructor(private keycloak: KeycloakService, @Inject('KEYCLOAK_AUTH_URL') private apimanKeycloakRestUrl: string) {
+ this.kcAdminClient = new KcAdminClient({
+ baseUrl: this.apimanKeycloakRestUrl,
+ realmName: 'Apiman'
+ });
+ this.kcAdminClient.setAccessToken(keycloak.getKeycloakInstance().token);
+ }
+
+ /**
+ * Get UUID of apiman-devportal client
+ */
+ private getDevPortalClientUUID() {
+ return from(this.kcAdminClient.clients.find())
+ .pipe(mergeAll())
+ .pipe(single(client => client.clientId === 'apiman-devportal'), map(client => client.id))
+ .pipe(share()); // fire only once and cache the result for other subscribers
+ }
+
+ /**
+ * Get the devportaluser user role
+ */
+ private getDevPortalUserRole() {
+ return from(this.kcAdminClient.roles.findOneByName({
+ name: 'devportaluser'
+ }));
+ }
+
+ /**
+ * Searchs a user from keycloak
+ * @param username the keycloak username
+ */
+ public searchUser(username) {
+ return from(this.kcAdminClient.users.find({username}));
+ }
+
+ /**
+ * Create user
+ * @param userToCreate the user to create
+ */
+ private createUser(userToCreate) {
+ return from(this.kcAdminClient.users.create({
+ firstName: userToCreate.firstName,
+ lastName: userToCreate.lastName,
+ email: userToCreate.email,
+ username: userToCreate.username,
+ enabled: true,
+ attributes: {generatedFromDevPortal: ['true']}
+ }));
+ }
+
+ /**
+ * Delete user by id
+ * @param userId the user id
+ */
+ private deleteUserById(userId) {
+ return from(this.kcAdminClient.users.del({
+ id: userId
+ }));
+ }
+
+ /**
+ * Set user password
+ * @param userId the user id
+ * @param password the user password
+ */
+ public setUserPassword(userId, password) {
+ return from(this.kcAdminClient.users.resetPassword({
+ id: userId,
+ credential: {
+ temporary: true,
+ type: 'password',
+ value: password,
+ },
+ }));
+ }
+
+ /**
+ * Create client role
+ * @param clientRoleName the client role name
+ */
+ public createClientRole(clientRoleName) {
+ return this.getDevPortalClientUUID()
+ .pipe(map(clientUUID => this.kcAdminClient.clients.createRole({
+ id: clientUUID,
+ name: clientRoleName
+ })));
+ }
+
+ /**
+ * Delete client role
+ * @param clientRoleName the client role name
+ */
+ public deleteClientRole(clientRoleName) {
+ return this.getDevPortalClientUUID()
+ .pipe(mergeMap(clientId => this.kcAdminClient.clients.delRole({id: clientId, roleName: clientRoleName})));
+ }
+
+ /**
+ * Get client role UUID
+ * @param clientUUID the client UUID
+ * @param clientRoleName the client role name
+ */
+ public getClientRoleUUID(clientUUID, clientRoleName) {
+ return from(this.kcAdminClient.clients.findRole({
+ id: clientUUID,
+ roleName: clientRoleName
+ })).pipe(map(roleObject => roleObject.id));
+ }
+
+ /**
+ * Add client role mapping to user
+ * @param userUUID user UUID
+ * @param clientUUID client UUID
+ * @param roleUUID role UUID
+ * @param roleName role name
+ */
+ public addClientRoleMappingToUser(userUUID, clientUUID, roleUUID, roleName) {
+ return from(this.kcAdminClient.users.addClientRoleMappings({
+ id: userUUID,
+ clientUniqueId: clientUUID,
+ roles: [{
+ id: roleUUID,
+ name: roleName
+ }]
+ }));
+ }
+
+ /**
+ * Add devportal role from user
+ * @param user the user
+ */
+ public addDevPortalRoleToUser(user) {
+ return this.getDevPortalUserRole().pipe(mergeMap(devPortalUserRole => this.kcAdminClient.users.addRealmRoleMappings({
+ id: user.id,
+ roles: [{
+ id: devPortalUserRole.id,
+ name: devPortalUserRole.name
+ }]
+ })));
+ }
+
+ removeRoleMapping(userUUID, roleUUID, roleName) {
+ return from(this.kcAdminClient.users.delRealmRoleMappings({
+ id: userUUID,
+ roles: [{
+ id: roleUUID,
+ name: roleName
+ }]
+ }));
+ }
+
+ /**
+ * Remove devportal role from user
+ * @param user the user
+ */
+ public removeDevPortalRoleFromUser(user) {
+ return this.getDevPortalUserRole()
+ .pipe(mergeMap(devPortalUserRole => this.removeRoleMapping(user.id, devPortalUserRole.id, devPortalUserRole.name)));
+ }
+
+ /**
+ * Add client role to user
+ * @param user the user
+ * @param clientRoleName the client role name
+ */
+ public addClientRoleToUser(user, clientRoleName) {
+ return this.createClientRole(clientRoleName)
+ .pipe(mergeMap((roleObject) => this.getDevPortalClientUUID()))
+ .pipe(mergeMap(clientUUID =>
+ this.getClientRoleUUID(clientUUID, clientRoleName)
+ .pipe(mergeMap(clientRoleUUID => this.addClientRoleMappingToUser(user.id, clientUUID, clientRoleUUID, clientRoleName))))
+ );
+ }
+
+ /**
+ * Find existing or create new user
+ * @param user the user
+ */
+ public findExistingOrCreateUser(user): Observable<UserRepresentation> {
+ return this.searchUser(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;
+ }));
+ }
+
+ /**
+ * Find user by user name
+ * @param username The username
+ */
+ public findUser(username) {
+ return from(this.kcAdminClient.users.find({
+ username
+ }));
+ }
+
+ /**
+ * 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';
+ }
+
+ /**
+ * 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): 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.removeDevPortalRoleFromUser(userToDelete);
+ }
+ }
+ }));
+ }
+
+}

MODIFY
src/app/api-data.service.ts
src/app/api-data.service.ts
import {Inject, Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {forkJoin, from, iif, merge, Observable, of} from 'rxjs';
-import {combineLatest, concatAll, defaultIfEmpty, filter, find, map, mergeAll, mergeMap, share, single, switchMap} from 'rxjs/operators';
-import { KeycloakService } from 'keycloak-angular';
-import KcAdminClient from 'keycloak-admin';
-import {emit} from 'cluster';
import has = Reflect.has;
-import {RoleMappingPayload} from 'keycloak-admin/lib/defs/roleRepresentation';
-import {RequiredActionAlias} from 'keycloak-admin/lib/defs/requiredActionProviderRepresentation';
-import UserConsentRepresentation from 'keycloak-admin/lib/defs/userConsentRepresentation';
-import CredentialRepresentation from 'keycloak-admin/lib/defs/credentialRepresentation';
-import FederatedIdentityRepresentation from 'keycloak-admin/lib/defs/federatedIdentityRepresentation';
-import UserRepresentation from 'keycloak-admin/lib/defs/userRepresentation';
/**
* Api Version
@@ -175,39 +165,12 @@ export interface KeycloakUser {
*/
export class ApiDataService {
- private kcAdminClient: KcAdminClient;
- private getDevPortalClientUUID: Observable<string>;
-
/**
* Contructor
* @param http The http client
* @param apimanUiRestUrl The apiman UI REST url
*/
- constructor(private http: HttpClient, private keycloak: KeycloakService, @Inject('APIMAN_UI_REST_URL') private apimanUiRestUrl: string, @Inject('KEYCLOAK_AUTH_URL') private apimanKeycloakRestUrl: string) {
- this.kcAdminClient = new KcAdminClient({
- baseUrl: this.apimanKeycloakRestUrl,
- realmName: 'Apiman'
- });
- this.kcAdminClient.setAccessToken(keycloak.getKeycloakInstance().token);
- this.getDevPortalClientUUID = from(this.kcAdminClient.clients.find())
- .pipe(mergeAll())
- .pipe(single(client => client.clientId === 'apiman-devportal'), map(client => client.id))
- .pipe(share());
-
-
- // const getAllDeveloperKeyCloakClientRoles = this.getDevPortalClientId.pipe(mergeMap(clientId =>
- // this.kcAdminClient.clients.listRoles({
- // id: clientId
- // })));
- }
-
- /**
- * Get developer by developer id
- * @param developerId The developer id
- */
- public getDeveloper(developerId: string) {
- const url = this.apimanUiRestUrl + '/developers/' + developerId;
- return this.http.get(url) as Observable<Developer>;
+ constructor(private http: HttpClient, @Inject('APIMAN_UI_REST_URL') private apimanUiRestUrl: string) {
}
/**
@@ -253,164 +216,4 @@ export class ApiDataService {
const url = this.apimanUiRestUrl + '/gateways/' + gatewayId + '/endpoint';
return this.http.get(url) as Observable<GatewayEndpoint>;
}
-
- /**
- * Get all available developers
- */
- public getAllDevelopers() {
- const url = this.apimanUiRestUrl + '/developers';
- return this.http.get(url) as Observable<Array<Developer>>;
- }
-
- private searchKeycloakUser = (keycloakUsername) => this.kcAdminClient.users.find({username: keycloakUsername});
-
- public isPasswordRequired(username) {
- return this.searchKeycloakUser(username).then(users => {
- return users.findIndex((user => user.username === username)) === -1;
- }).catch(error => {
- console.error(error);
- return Promise.resolve(true);
- });
- }
-
- /**
- * Create new developer
- * @param developer the developer to create
- */
- public createNewDeveloper(developer: Developer, keycloakUser: KeycloakUser) {
- const url = this.apimanUiRestUrl + '/developers';
-
- //1. insert developer
- //response has developer object with developer id
- const insertDeveloperToApiman = (this.http.post(url, developer) as Observable<Developer>)
- .pipe(map(developerInserted => developerInserted.id))
- .toPromise();
-
- // const searchKeycloakUser = (keycloakUsername) => this.kcAdminClient.users.find({username: keycloakUsername});
-
- const addUserToKeycloak = (userToInsert) => this.kcAdminClient.users.create({
- firstName: userToInsert.firstName,
- lastName: userToInsert.lastName,
- email: userToInsert.email,
- username: userToInsert.username,
- enabled: true,
- attributes: {generatedFromDevPortal: ['true']}
- });
-
- const setPasswordForUser = (userId, password) => password && password.length !== 0 ? this.kcAdminClient.users.resetPassword({
- id: userId,
- credential: {
- temporary: true,
- type: 'password',
- value: password,
- },
- }) : Promise.reject('no password set');
-
- const createKeycloakRoleRequest = (developerId) => this.getDevPortalClientUUID.pipe(map(clientUUID => this.kcAdminClient.clients.createRole({
- id: clientUUID,
- name: developerId
- }))).toPromise();
-
- const getRoleUUID = (clientUUID, roleName) => this.kcAdminClient.clients.findRole({
- id: clientUUID,
- roleName
- }).then(roleObject => roleObject.id);
-
- const addClientRoleMappingToUser = (userKeycloakUUID, clientUUID, roleUUID, roleName) => this.kcAdminClient.users.addClientRoleMappings({
- id: userKeycloakUUID,
- clientUniqueId: clientUUID,
- roles: [{
- id: roleUUID,
- name: roleName
- }]
- });
-
- return from(insertDeveloperToApiman.then(apimanDeveloperId =>
- this.searchKeycloakUser(keycloakUser.username).then(foundUsers => {
- let chain: Promise<string> = null;
- if (foundUsers.length === 0) {
- chain = addUserToKeycloak(keycloakUser)
- .then(insertedUser => setPasswordForUser(insertedUser.id, keycloakUser.password)
- .catch(reason => reason === 'no password set')
- .then(() => Promise.resolve(insertedUser.id)));
- } else {
- chain = Promise.resolve(foundUsers[0].id);
- }
- return chain.then((insertedKeycloakUserId) => createKeycloakRoleRequest(apimanDeveloperId)
- .then((roleObject) => this.getDevPortalClientUUID.toPromise().then(clientUUID =>
- getRoleUUID(clientUUID, apimanDeveloperId)
- .then(roleUUID => addClientRoleMappingToUser(insertedKeycloakUserId, clientUUID, roleUUID, apimanDeveloperId))
- )
- ));
- })
- ));
- }
-
- /**
- * Update a developer
- * @param developer the developer to update
- */
- public updateDeveloper(developer: Developer) {
- const url = this.apimanUiRestUrl + '/developers/' + developer.id;
- return this.http.put(url, {
- name: developer.name,
- clients: developer.clients
- });
- }
-
- private isUserGeneratedFromDevPortal(user: UserRepresentation) {
- return user.attributes && user.attributes.generatedFromDevPortal && user.attributes.generatedFromDevPortal.length > 0 && user.attributes.generatedFromDevPortal[0] === 'true';
- }
-
- /**
- * Delete a developer
- * @param developer the developer to update
- */
- public deleteDeveloper(developer: Developer) {
- const url = this.apimanUiRestUrl + '/developers/' + developer.id;
- const deleteDeveloperFromApiman = this.http.delete(url);
-
- const deleteKeycloakClientRole = from(this.getDevPortalClientUUID.pipe(mergeMap(clientId => this.kcAdminClient.clients.delRole({
- id: clientId,
- roleName: developer.id
- }))));
-
- const deleteKeycloakUser = from(this.kcAdminClient.users.find({
- username: developer.name
- }).then(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.kcAdminClient.users.del({
- id: userToDelete.id
- });
- }
- }
- return Promise.resolve();
- }));
- return forkJoin(deleteDeveloperFromApiman, deleteKeycloakClientRole, deleteKeycloakUser);
- }
-
- /**
- * Get all available clients
- */
- public getAllClients() {
- const url = this.apimanUiRestUrl + '/search/clients';
- const searchQuery = {
- filters: [{
- name: 'name',
- value: '*',
- operator: 'like'
- }],
-
- paging: {
- page: '1',
- pageSize: '10000'
- }
- };
- return (this.http.post(url, searchQuery) as Observable<ClientSearchResult>)
- .pipe(mergeMap( searchResult => searchResult.beans));
- }
-
}

MODIFY
src/app/pas-header/pas-header.component.ts
src/app/pas-header/pas-header.component.ts
@@ -6,15 +6,12 @@ import { KeycloakService } from 'keycloak-angular';
templateUrl: './pas-header.component.html',
styleUrls: ['./pas-header.component.scss']
})
-export class PasHeaderComponent implements OnInit {
+export class PasHeaderComponent {
public user: string = this.keycloak.getKeycloakInstance().profile.username;
constructor(private keycloak: KeycloakService) { }
- ngOnInit() {
- }
-
public logout() {
sessionStorage.setItem('apiman_keycloak_token', '');
sessionStorage.setItem('apiman_keycloak_refresh_token', '');

GW-263: Insert and Delete Developers with Keycloak user creation