user.service.ts 9.24 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1
2
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
Lukas Burgey's avatar
Lukas Burgey committed
3

4
5
import { Observable, BehaviorSubject, of, EMPTY } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
Lukas Burgey's avatar
Lukas Burgey committed
6

Lukas Burgey's avatar
Lukas Burgey committed
7
8
9
10
11
12
import { CookieService } from 'ngx-cookie-service';
import { StompConfig, StompRService } from '@stomp/ng2-stompjs';
import { Message } from '@stomp/stompjs';

import { SnackBarService } from './snackbar.service';
import { IdpService } from './idp.service';
Lukas Burgey's avatar
Lukas Burgey committed
13

14
import { User, Update, State, Deployment, SSHKey, NewSSHKey, Group, IdP, Service } from './types/types.module';
Lukas Burgey's avatar
Lukas Burgey committed
15

Lukas Burgey's avatar
Lukas Burgey committed
16
17
18

@Injectable()
export class UserService {
19
20
  private initialized = false;
  private loggedIn: boolean = false;
Lukas Burgey's avatar
Lukas Burgey committed
21

22
  private user: User;
Lukas Burgey's avatar
Lukas Burgey committed
23
  private user$: BehaviorSubject<User>;
Lukas Burgey's avatar
Lukas Burgey committed
24

25
  private sshKeys: SSHKey[] = new Array<SSHKey>();
Lukas Burgey's avatar
Lukas Burgey committed
26
  private sshKeys$ = new BehaviorSubject<SSHKey[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
27

28
  private deployments: Map<number, Deployment> = new Map([]);
Lukas Burgey's avatar
Lukas Burgey committed
29
  private deployments$ = new BehaviorSubject<Deployment[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
30

Lukas Burgey's avatar
Lukas Burgey committed
31
  public groupSelector = (user: User) => user ? user.groups : [];
32
  public serviceSelector = (user: User) => user ? user.services ? user.services : [] : [];
Lukas Burgey's avatar
Lukas Burgey committed
33
34

  constructor(
Lukas Burgey's avatar
Lukas Burgey committed
35
36
37
38
    private cookieService: CookieService,
    private http: HttpClient,
    private snackBar: SnackBarService,
    private idpService: IdpService,
Lukas Burgey's avatar
Lukas Burgey committed
39
    private stompService: StompRService,
Lukas Burgey's avatar
Lukas Burgey committed
40
  ) {
Lukas Burgey's avatar
Lukas Burgey committed
41
    this.user$ = new BehaviorSubject(null);
42
    this.connect();
43
44
  }

45
46
  // PRIVATE API
  private connect(): void {
Lukas Burgey's avatar
Lukas Burgey committed
47
48
    this.fetch();

49
50
    this.subscribeUser().subscribe(
      (newUser: User) => {
51
        console.log('user$:', newUser);
Lukas Burgey's avatar
Lukas Burgey committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

        if (newUser == undefined || newUser == null) {
          // LOGGED OUT
          // show the logout to the user
          if (this.loggedIn) {
            this.snackBar.open('Logged out');

            // purge all values
            this.stompService.disconnect();
            this.user = undefined;
            this.deployments = new Map([]);
            this.deployments$.next([]);
            this.sshKeys = [];
            this.sshKeys$.next([]);
          }


          this.loggedIn =  false;
        } else {
          // LOGGED IN
          // show the login to the user
          if (!this.loggedIn) {
            this.snackBar.open('Logged in');
          }

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

          if (newUser.id) {
            this.connectLiveUpdates(newUser.id);
          }
          if (newUser.ssh_keys) {
            this.sshKeys = newUser.ssh_keys;
            this.sshKeys$.next(this.sshKeys);
          }
          if (newUser.deployments) {
            newUser.deployments.forEach(
              (newDep: Deployment) => {
                this.deployments.set(newDep.id,  newDep);
              }
            );
            this.deployments$.next(Array.from(this.deployments.values()));
          }

Lukas Burgey's avatar
Lukas Burgey committed
94
          this.loggedIn = true;
95
96
97
          this.initialized = true;
        }
      },
Lukas Burgey's avatar
Lukas Burgey committed
98
99
      this.handleError(true),
      () => console.log('user$ is complete'),
100
    );
Lukas Burgey's avatar
Lukas Burgey committed
101
102
  }

103
  private connectLiveUpdates(userID: number): void {
Lukas Burgey's avatar
Lukas Burgey committed
104
    // handle with care
105
    let login = userID
Lukas Burgey's avatar
Lukas Burgey committed
106
    let passcode = this.cookieService.get('sessionid');
Lukas Burgey's avatar
Lukas Burgey committed
107

Lukas Burgey's avatar
Lukas Burgey committed
108
109
    const stompConfig: StompConfig = {
      // Which server?
110
      url: 'wss://'+window.location.host+'/ws',
111

Lukas Burgey's avatar
Lukas Burgey committed
112
113
114
115
116
117
      // Headers
      // Typical keys: login, passcode, host
      headers: {
        login: 'webpage-client:' + login,
        passcode: passcode,
      },
Lukas Burgey's avatar
Lukas Burgey committed
118

Lukas Burgey's avatar
Lukas Burgey committed
119
120
121
122
123
124
      // How often to heartbeat?
      // Interval in milliseconds, set to 0 to disable
      heartbeat_in: 0, // Typical value 0 - disabled
      heartbeat_out: 20000, // Typical value 20000 - every 20 seconds
      // Wait in milliseconds before attempting auto reconnect
      // Set to 0 to disable
125
126
      // Typical value 15000 (15 seconds)
      reconnect_delay: 15000,
127

Lukas Burgey's avatar
Lukas Burgey committed
128
129
130
      // Will log diagnostics on console
      debug: false,
    };
Lukas Burgey's avatar
Lukas Burgey committed
131
132
    this.stompService.config = stompConfig;
    this.stompService.initAndConnect();
Lukas Burgey's avatar
Lukas Burgey committed
133

Lukas Burgey's avatar
Lukas Burgey committed
134
    let subscription = this.stompService.subscribe(
135
      '/exchange/users/' + userID.toString()
Lukas Burgey's avatar
Lukas Burgey committed
136
    );
137

Lukas Burgey's avatar
Lukas Burgey committed
138
139
    subscription.subscribe(
      (message: Message) => {
140
141
142
        let update: Update = JSON.parse(message.body);
        console.log('update:', update);

Lukas Burgey's avatar
Lukas Burgey committed
143
        if (update.error && update.error != '') {
144
          console.log(update.error);
Lukas Burgey's avatar
Lukas Burgey committed
145
          this.snackBar.open(update.error);
Lukas Burgey's avatar
Lukas Burgey committed
146
        }
147
148
149

        if (update.deployment) {
          this.updateDeployment(update.deployment);
Lukas Burgey's avatar
Lukas Burgey committed
150
        }
Lukas Burgey's avatar
Lukas Burgey committed
151
      },
152
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
153
    );
Lukas Burgey's avatar
Lukas Burgey committed
154
  }
155

Lukas Burgey's avatar
Lukas Burgey committed
156
157
158
159
160
161
  private updateDeployment(dep: Deployment): void {
    if (dep) {
      this.deployments.set(dep.id, dep);
      this.deployments$.next(Array.from(this.deployments.values()));
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
162

Lukas Burgey's avatar
Lukas Burgey committed
163
  private updateState(update: State): void {
164
165
166
    if (update) {
      // report an occured error
      if (update.error) {
167
168
        console.log(update.error);
        this.snackBar.open(update.error);
169
      }
Lukas Burgey's avatar
Lukas Burgey committed
170

Lukas Burgey's avatar
Lukas Burgey committed
171
172
      this.user = update.user;
      this.user$.next(this.user);
173
174

    } else {
Lukas Burgey's avatar
Lukas Burgey committed
175
      this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
176
177
178
    }
  }

179
  private fetch(): void {
Lukas Burgey's avatar
Lukas Burgey committed
180
    this.http.get<State>('/backend/api/state').subscribe(
181
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
182
      this.handleError(false, 'Error fetching state. Try again later'),
Lukas Burgey's avatar
Lukas Burgey committed
183
184
185
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
186
187
188
189
190
191
  private handleError(fetch: boolean, msg?: string) {
    return (error: any) => {
      if (error.status == 403) {
        this.login();
        return
      }
192

Lukas Burgey's avatar
Lukas Burgey committed
193
194
195
196
197
198
199
      if (msg) {
        this.snackBar.open(msg);
      }
      console.log('fetch:', error);
      if (fetch) {
        this.fetch();
      }
200
201
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
202

Lukas Burgey's avatar
Lukas Burgey committed
203
204
205
  private catchError(fetch: boolean, msg?: string) {
    return (error: any) => {
      this.handleError(fetch, msg)(error);
206
      return EMPTY;
Lukas Burgey's avatar
Lukas Burgey committed
207
    }
Lukas Burgey's avatar
Lukas Burgey committed
208
209
  }

Lukas Burgey's avatar
Lukas Burgey committed
210
  private logErrorAndFetch(error: any)  {
211
212
    if (error.status === 403) {
      this.login();
Lukas Burgey's avatar
Lukas Burgey committed
213
    }
214
215
216
217
218

    console.log(error);
    this.snackBar.open('Error');
    this.fetch();
    return Observable.throw(error);
Lukas Burgey's avatar
Lukas Burgey committed
219
220
  }

Lukas Burgey's avatar
Lukas Burgey committed
221
222
223
224
225
226
227
228
  // PUBLIC API
  public serviceDescription(service: Service): string {
    if (service.description != "") {
      return service.description;
    }
    return "No description";
  }

229
230
231
232
  public login(idp?: IdP): void {
    if (idp) {
      this.idpService.setIdPPreference(idp);
    }
233

234
    window.location.href = '/backend/auth/v1/request';
235
236
  }

Lukas Burgey's avatar
Lukas Burgey committed
237
238
  public logout(): void {
    this.http.post<State>('/backend/auth/v1/logout', {}).subscribe(
239
240
      (state: State) => this.updateState(state),
      this.logErrorAndFetch,
241
242
243
    );
  }

244
  public sentQuestionnaire(stateItemID: number, answers: Object) {
Lukas Burgey's avatar
Lukas Burgey committed
245
246
    return this.http.post<Deployment>('/backend/api/questionnaire?id='+String(stateItemID), answers).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
247
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
248
249
250
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
251
  public deleteUser() {
Lukas Burgey's avatar
Lukas Burgey committed
252
253
254
    return this.http.delete('/backend/api/delete_user').subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
Lukas Burgey's avatar
Lukas Burgey committed
255
          this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
256
257
          this.snackBar.open('Deleted user from server');
        }
Lukas Burgey's avatar
Lukas Burgey committed
258
      },
259
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
260
261
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
262

263
  public uploadSshKey(key: NewSSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
264
    console.log('Uploading key:', key);
Lukas Burgey's avatar
Lukas Burgey committed
265
266
267
268
    const body = {
      'type': 'add',
      'key': key,
    };
Lukas Burgey's avatar
Lukas Burgey committed
269
    return this.http.post<SSHKey>('/backend/api/sshkey', body).subscribe(
270
      (newKey: SSHKey) => {
Lukas Burgey's avatar
Lukas Burgey committed
271
272
        this.sshKeys.push(newKey);
        this.sshKeys$.next(this.sshKeys);
273
      },
274
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
275
276
277
    );
  }

278
  public removeSshKey(key: SSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
279
    console.log('Deleting key:', key);
Lukas Burgey's avatar
Lukas Burgey committed
280
281
282
283
284
285
286
287
288
289
290
    return this.http.post('/backend/api/sshkey', {
      'type': 'remove',
      'id': key.id,
    }).subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
          this.sshKeys = this.sshKeys.filter(
            k => key.id != k.id
          );
          this.sshKeys$.next(this.sshKeys);
        }
291
      },
292
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
293
294
    );
  }
295

296
  public changeDeployment(action: string, group: Group): Observable<Deployment> {
297
298
299
300
    const body = {
      'type': action,
      'group': group.id,
    };
301
302
    return this.http.post<Deployment>('/backend/api/deployments', body).pipe(
      catchError(this.catchError(true, "Error changing deployment")),
303
304
305
306
307
    );
  }

  // DATA SERVICE API
  //
Lukas Burgey's avatar
Lukas Burgey committed
308
  public subscribeSpecific<T>(selector: (user: User) => T): Observable<T> {
309
    return this.subscribeUser().pipe(map(selector));
310
311
  }

Lukas Burgey's avatar
Lukas Burgey committed
312
313
  public subscribeSSHKeys(): Observable<SSHKey[]> {
    return this.sshKeys$.asObservable();
314
315
  }

316
317
  public subscribeUser(): Observable<User> {
    return this.user$.asObservable();
318
319
  }

320
321
  public subscribeDeployments(): Observable<Deployment[]> {
    return this.deployments$.asObservable();
322
323
  }

Lukas Burgey's avatar
Lukas Burgey committed
324
325
326
  public subscribeGroups(): Observable<Group[]> {
    let groupSelector = (user: User) => user ? user.groups : [];
    return this.subscribeSpecific<Group[]>(groupSelector);
327
328
  }

Lukas Burgey's avatar
Lukas Burgey committed
329

330
  public subscribeServiceDeployment(service: Service): Observable<Deployment> {
331
332
333
    return this.subscribeDeployments().pipe(
      map(
        (deployments: Deployment[]) => {
334
335
          return deployments.find(
            (dep: Deployment) => dep.service ? dep.service.id == service.id : false
336
337
          );
        }
338
      ),
339
340
341
    );
  }

342
  public subscribeGroupDeployment(group: Group): Observable<Deployment> {
343
344
345
346
347
348
349
    return this.subscribeDeployments().pipe(
      map((deployments: Deployment[]) => {
          return deployments.find(
            (dep: Deployment) => dep.group == group.id
          );
        }
      ),
350
351
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
352
}