authorisation.service.ts 12.9 KB
Newer Older
1
2
import { Injectable } from '@angular/core';
import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
3
import { Observable, Subject, BehaviorSubject } from 'rxjs';
4
import { catchError, map, tap, take,filter,skip } from 'rxjs/operators';
5
import { ActivatedRoute, Router } from '@angular/router';
Chris Hines's avatar
Chris Hines committed
6
import {LocationStrategy, Location} from '@angular/common';
7
8
9
// import { keypair } from 'keypair';
import * as keypair from 'keypair';
import * as forge from "node-forge";
Chris Hines's avatar
Chris Hines committed
10
import { Identity, AuthToken, KeyCert, SshAuthzServer } from './identity';
Chris Hines's avatar
Chris Hines committed
11
import {BackendSelectionService} from './backend-selection.service';
Chris Hines's avatar
Chris Hines committed
12
13
14
15
import { throwError } from 'rxjs';


export class SshauthzServer {}
16
17
18
19
20

@Injectable({
  providedIn: 'root'
})
export class AuthorisationService {
Chris Hines's avatar
Chris Hines committed
21
  // public token: BehaviorSubject<AuthToken>;
Chris Hines's avatar
Chris Hines committed
22
  public token: BehaviorSubject<AuthToken>;
23
24
25
  public sshAuthzServers: BehaviorSubject<SshAuthzServer[]>;
  public loggedInAuthZ: BehaviorSubject<SshAuthzServer[]>;
  public loggedOutAuthZ: BehaviorSubject<SshAuthzServer[]>;
Chris Hines's avatar
Chris Hines committed
26
  public readyToNavigate: Subject<[Boolean,string]>;
Chris Hines's avatar
Chris Hines committed
27
  public progress: Subject<string>;
28
  // private keyCert: Subject<KeyCert>;
Chris Hines's avatar
Chris Hines committed
29
  public backendURI: string;
Chris Hines's avatar
Chris Hines committed
30
31
32
  public statusMsg: BehaviorSubject<string>;
  public agentContents: BehaviorSubject<any>;
  public keys: KeyCert[];
33
34
35
36
37
38


  constructor(private http: HttpClient,
              private locationStrategy: LocationStrategy,
              private route: ActivatedRoute,
              private router: Router,
Chris Hines's avatar
Chris Hines committed
39
              private backendSelectionService: BackendSelectionService,
Chris Hines's avatar
Chris Hines committed
40
              private location: Location) {
Chris Hines's avatar
Chris Hines committed
41
                this.token = new BehaviorSubject<AuthToken>(null);
Chris Hines's avatar
Chris Hines committed
42
43
                this.readyToNavigate = new Subject<[Boolean,string]>();
                this.readyToNavigate.next([false,'']);
Chris Hines's avatar
Chris Hines committed
44
45
46
                this.progress = new Subject<string>();
                this.progress.next("");
                // this.token.subscribe(token => this.getCert(token));
47
                this.route.fragment.subscribe(frag => this.storeToken(frag));
48
49
50
                this.sshAuthzServers = new BehaviorSubject<SshAuthzServer[]>([]);
                this.loggedInAuthZ = new BehaviorSubject<SshAuthzServer[]>([]);
                this.loggedOutAuthZ = new BehaviorSubject<SshAuthzServer[]>([]);
51
                this.backendURI = null;
52
                this.agentContents = new BehaviorSubject([]);
Chris Hines's avatar
Chris Hines committed
53
                this.keys = [];
Chris Hines's avatar
Chris Hines committed
54
                this.getSshAuthzServers();
Chris Hines's avatar
Chris Hines committed
55
                this.keys = [];
56
57
58
59
    this.backendSelectionService.apiserver.pipe(filter((v) => v !== null && v !== undefined))
      .subscribe((value) => { this.backendURI = value.tes ; this.updateAgentContents() })
    this.agentContents.subscribe((value) => this.updateLoggedAuthZ());
    this.sshAuthzServers.subscribe((value) => this.updateLoggedAuthZ());
Chris Hines's avatar
Chris Hines committed
60
61
62
63
 }

 public setStatusMsg(statusMsg: BehaviorSubject<any>) {
   this.statusMsg = statusMsg;
Chris Hines's avatar
Chris Hines committed
64
65
 }

66
67
68
69
70
71
72
73
74
75
76
77
 storeLocalAuthZ(authz: any) {
   try {
     localStorage.setItem('localauthservers',JSON.stringify(authz));
   } catch {
   }
   this.getSshAuthzServers();
 }
 removeLocalAuthZ() {
   localStorage.removeItem('localauthservers');
   this.getSshAuthzServers();
 }

Chris Hines's avatar
Chris Hines committed
78
79
80
81
82
83
 getSshAuthzServers() {
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: false};
   this.http.get<SshAuthzServer[]>('./assets/config/authservers.json',options)
                    .pipe(catchError(this.handleError('getSshAuthzServers')))
                    .subscribe(resp => this.updateSshAuthzServers(resp));
84
 }
Chris Hines's avatar
Chris Hines committed
85

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
 private updateLoggedAuthZ() {
     let agentContents = this.agentContents.value
     let authzServers = this.sshAuthzServers.value
     let loggedin = []
     let loggedout = []
     var found: boolean;
     for (let s of authzServers) {
         found=false;
         for (let cert of agentContents) {
           if ('Signing CA' in cert) {
             for (let ca of cert['Signing CA']) {
               if (ca == s.cafp) {
                 loggedin.push(s)
                 found=true;
                 continue;
               }
             }
           }
           if (found) {
               continue;
           }
         }
         if (!found)  {
           loggedout.push(s)
         }
     }
     this.loggedOutAuthZ.next(loggedout);
     this.loggedInAuthZ.next(loggedin);
 }
        

Chris Hines's avatar
Chris Hines committed
117
 updateSshAuthzServers(resp) {
118
119
120
121
122
123
124
125
126
   var auths: SshAuthzServer[];
   var localauths: SshAuthzServer[] = [];
   var server: SshAuthzServer;
   auths = <SshAuthzServer[]>resp;
   try {
       localauths = JSON.parse(localStorage.getItem('localcomputesites'))
   } catch {
       localauths = []
   }
Chris Hines's avatar
Chris Hines committed
127
128
129
130
   if (localauths !== null) {
       for (server of localauths) {
           auths.push(server);
       }
131
132
   }
   this.sshAuthzServers.next(auths);
Chris Hines's avatar
Chris Hines committed
133
134
 }

135
136
137
138
139
140
141
 storeToken(frag: string) {
   if (frag === undefined || frag == null) {
       return;
   }
   let tokenmatch = null;
   let statematch = null;
   if (!(frag === undefined) && !(frag == null)) {
Chris Hines's avatar
Chris Hines committed
142
143
     tokenmatch = frag.match(/access_token\=([^&]+)(&|$)/);
     statematch = frag.match(/state\=([^&]+)(&|$)/);
144
145
146
147
148
149
150
   }
   if (tokenmatch == null || statematch == null) {
     return;
   }

   let accesstoken = tokenmatch[1];
   let state = statematch[1];
Chris Hines's avatar
Chris Hines committed
151
   // this.router.navigate(['/']);
152
153
154

   //Verify that the state matched the nonce we used when initiating login
   let tuple = JSON.parse(localStorage.getItem('authservice'));
Chris Hines's avatar
Chris Hines committed
155
156
157
158
159
   if (tuple == null) {
     console.log('unable to get the authservice from localStorage');
     console.log(localStorage.getItem('authservice'));
     return
   }
160
161
162
163
   if (tuple[1] != state) {
     return
   }

Chris Hines's avatar
Chris Hines committed
164
165
   this.token.next(new AuthToken(tokenmatch[1],tuple[0]));
   //this.token = new AuthToken(tokenmatch[1],tuple[0]);
166
167
168
169


 }

170
 public getCert() {
Chris Hines's avatar
Chris Hines committed
171
   var token: AuthToken;
172
173
174
175
176
   token = this.token.value;
   //   if (this.backendSelectionService.apiserver.value === null) {
   // this.backendSelectionService.apiserver.pipe(take(1)).subscribe(() => this.getCert())
   // return
   //}
Chris Hines's avatar
Chris Hines committed
177
178
   if (token === null || token === undefined || token.token === undefined || token.token === '' || token.token === null) {
     this.token.pipe(skip(1),take(1)).subscribe(() => this.getCert())
179
180
     return
   }
Chris Hines's avatar
Chris Hines committed
181
   if (this.backendSelectionService.apiserver.value === null || this.backendSelectionService.apiserver.value === undefined) {
182
183
     this.backendSelectionService.apiserver.pipe(skip(1),take(1)).subscribe(() => this.getCert())
     return
Chris Hines's avatar
Chris Hines committed
184
   }
185

186

Chris Hines's avatar
Chris Hines committed
187
188
189
190
191
   let starttime = new Date();
   let newkeypair = keypair();
   let publicKey = forge.pki.publicKeyFromPem(newkeypair.public);
   let sshpub = forge.ssh.publicKeyToOpenSSH(publicKey, 'sv2@monash.edu');
   let endtime = new Date();
192

Chris Hines's avatar
Chris Hines committed
193
194
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
195

Chris Hines's avatar
Chris Hines committed
196
   let data = {'token': token.token, 'pubkey': sshpub, 'signing_url': token.sshauthzservice.sign};
197

198
   this.http.post<any>(this.backendSelectionService.apiserver.value.tes+'/getcert',data, options)
Chris Hines's avatar
Chris Hines committed
199
200
201
                      .pipe(catchError(this.handleError([])))
                      .subscribe(resp => this.makeKeyCert(newkeypair.private, resp, token.sshauthzservice),
                                 error => this.signingError(error,token.sshauthzservice));
202
203
 }

Chris Hines's avatar
Chris Hines committed
204
205
206
207
208
209
210
public getKeys(id?: Identity) {
  try{
    return JSON.parse(sessionStorage.getItem('keys'));
  } catch {
    return [];
  }
}
Chris Hines's avatar
Chris Hines committed
211
212

 makeKeyCert(key: string, resp, sshauthzservice: SshAuthzServer) {
213
214
215
   let keyCert = new KeyCert()
   keyCert.key = key;
   keyCert.cert = resp['cert'];
Chris Hines's avatar
Chris Hines committed
216
217
218
219
220
221
222
223
224
225
226
227
   var keys: KeyCert[] = [];
   try{
     keys = JSON.parse(sessionStorage.getItem('keys'));
   } catch {
     keys = [];
   }
   if (keys === null) {
     keys = [];
   }
   keys.push(keyCert);
   sessionStorage.setItem('keys',JSON.stringify(keys))
   this.sshAdd(keyCert);
Chris Hines's avatar
Chris Hines committed
228
229
230
231
232
   // this.tesService.keyCert.next(keyCert);
   // As soon as the certificate has been generated, we log back out of the signing server
   if (!(sshauthzservice.logout === null)) {
     window.open(sshauthzservice.logout);
   }
233
   let path=localStorage.getItem('path');
234
235
236
237
238
   console.log('I should have a keycert now');
   this.updateAgentContents();
   console.log('requested agent contens update');
   // only navigate once the agent contents has been refreshed
   this.loggedInAuthZ.pipe(take(1)).subscribe( () => {this.readyToNavigate.next([true,path])});
Chris Hines's avatar
Chris Hines committed
239
240
241
242
243
244
245
 }

 public querySshAgentError(error: any) {
   this.agentContents.next([]);
   if (error.status == 0) {
     this.statusMsg.next("A network error occured. Are you connected to the internet?")
   }
246
   this.statusMsg.next("Error querying ssh agent");
Chris Hines's avatar
Chris Hines committed
247
248
249
 }

 public updateAgentContents() {
250
251
252
253
254
255
   if (this.backendURI == null) {
     return
   }
   console.log('attempting to update agent contents');
   console.log(this.backendSelectionService.apiserver)
   console.log(this.backendURI);
Chris Hines's avatar
Chris Hines committed
256
257
258
   if (this.statusMsg !== undefined) {
    this.statusMsg.next("Updating the list of available accounts");
   };
Chris Hines's avatar
Chris Hines committed
259
260
261
262
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   var anyvar: any;
   this.http.get<any>(this.backendURI+'/sshagent',options)
263
                     .pipe(catchError(this.handleError(anyvar)))
Chris Hines's avatar
Chris Hines committed
264
                     .subscribe(resp => { this.agentContents.next(resp); this.statusMsg.next("") },
Chris Hines's avatar
Chris Hines committed
265
266
267
268
269
                                error => this.querySshAgentError(error));
                     // .subscribe(resp => this.computeSitesService.updateIdentities(resp),
                     //            error => this.httperror(error))
 }

270

Chris Hines's avatar
Chris Hines committed
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
 private killAgent() {
   this.statusMsg.next("Logging out")
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   var anyvar: any;
   this.http.delete<any>(this.backendURI+'/sshagent',options)
                     .pipe(catchError(this.handleError(anyvar)))
                     .subscribe(resp => this.updateAgentContents(),
                                error => this.httperror(error));
 }

 public logout(): Boolean {
   this.killAgent();
   return true;
 }



 public sshAdd(keyCert: KeyCert) {
   if (keyCert.key == undefined) {
     return;
   }
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   var anyvar: any;

   this.statusMsg.next("Authorising ...")
   let data = {'key': keyCert.key, 'cert': keyCert.cert};
   this.http.post<any>(this.backendURI+'/sshagent',data,options)
                       .pipe(catchError(this.handleError(anyvar)))
                       .subscribe(resp => this.updateAgentContents(),
                                  error => this.httperror(error))
303
304
 }

Chris Hines's avatar
Chris Hines committed
305
 public login(authservice: SshAuthzServer) {
306
   let redirect_uri = window.location.origin+this.locationStrategy.getBaseHref()+"sshauthz_callback";
307
   let nonce=Math.random().toString(36).substring(2, 15)
Chris Hines's avatar
Chris Hines committed
308
309
310



311
   localStorage.setItem('authservice', JSON.stringify([authservice,nonce]));
Chris Hines's avatar
Chris Hines committed
312
   localStorage.setItem('path', this.location.path());
Chris Hines's avatar
Chris Hines committed
313
314
315
316
317
   if (authservice.scope == null) {
     window.location.assign(authservice.authorise+"?response_type=token&redirect_uri="+redirect_uri+"&state="+nonce+"&client_id="+authservice.client_id);
   } else {
     window.location.assign(authservice.authorise+"?response_type=token&redirect_uri="+redirect_uri+"&state="+nonce+"&client_id="+authservice.client_id+"&scope="+authservice.scope);
   }
318
319
 }

Chris Hines's avatar
Chris Hines committed
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
 // private handleError(error: HttpErrorResponse) {
 //   if (error.error instanceof ErrorEvent) {
 //     console.log('instance of ErrorEvent');
 //     console.log(error);
 //     // A client-side or network error occurred. Handle it accordingly.
 //     return throwError("Hmm, that didn't work. If you're using a local connection, please make sure Strudel-TES is running.");
 //   } else {
 //     console.log('NOT an instance of ErrorEvent');
 //     console.log(error);
 //     // Not sure if this code works correctly. It should update identities in case the error is
 //     // that the user isn't allowed to run the job
 //
 //     var re = /identity/gi;
 //     let searchresult = error.error.message.search(re);
 //     if (searchresult != -1) {
 //       // this.getIdentities();
 //       return throwError('login expired, refreshing');
 //     }
 //     return throwError(error.error.message)
 //     // this.statusMsg.next("There was an error submitting that job. The backend gave me the message: " + error.error.message);
 //
 //   }

343
 private httperror(error: any) {
344
345
   this.statusMsg.next('There was an error logging in or generating crypto tokens');
   console.error(error);
346
 }
347
 private signingError(error: any,sshauthzservice: SshAuthzServer) {
348
   this.statusMsg.next('You don\'t appear to have an account on '+sshauthzservice.name);
349
   console.log(error);
Chris Hines's avatar
Chris Hines committed
350
351
352
   if (!(sshauthzservice.logout === null)) {
     window.open(sshauthzservice.logout);
   }
353
354
   //let path=localStorage.getItem('path');
   //this.readyToNavigate.next([true,path]);
Chris Hines's avatar
Chris Hines committed
355
 }
356
 
357

Chris Hines's avatar
Chris Hines committed
358
 private handleError<T> (result?: T) {
359
   return (error: any): Observable<T> => {
Chris Hines's avatar
Chris Hines committed
360
     if (error.status == 500) {
Chris Hines's avatar
Chris Hines committed
361
       return throwError("The backend server encountered and error. Please try again in a few minutes")
Chris Hines's avatar
Chris Hines committed
362
363
364
     }
     return throwError(error.message);
     // return of(result as T);
365
366
367
   };
 }
}