authorisation.service.ts 13.3 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
 storeToken(frag: string) {
Chris Hines's avatar
Chris Hines committed
136
   console.log('storing the token fragment');
137
   if (frag === undefined || frag == null) {
Chris Hines's avatar
Chris Hines committed
138
       console.log('framgned not defined, not storing');
139
140
141
142
143
       return;
   }
   let tokenmatch = null;
   let statematch = null;
   if (!(frag === undefined) && !(frag == null)) {
Chris Hines's avatar
Chris Hines committed
144
145
     tokenmatch = frag.match(/access_token\=([^&]+)(&|$)/);
     statematch = frag.match(/state\=([^&]+)(&|$)/);
146
147
148
149
150
151
152
   }
   if (tokenmatch == null || statematch == null) {
     return;
   }

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

   //Verify that the state matched the nonce we used when initiating login
Chris Hines's avatar
Chris Hines committed
156
   let tuple = JSON.parse(sessionStorage.getItem('authservice'));
157
   if (tuple == null) {
Chris Hines's avatar
Chris Hines committed
158
     console.log('token found, but no authService defined');
Chris Hines's avatar
Chris Hines committed
159
     let data = sessionStorage.getItem('authservice');
Chris Hines's avatar
Chris Hines committed
160
     console.log(data);
161
162
     return
   }
163
164
165
166
   if (tuple[1] != state) {
     return
   }

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


 }

173
 public getCert() {
Chris Hines's avatar
Chris Hines committed
174
   var token: AuthToken;
175
   token = this.token.value;
176
   console.log('entered getCert');
Chris Hines's avatar
Chris Hines committed
177
   if (token === null || token === undefined || token.token === undefined || token.token === '' || token.token === null) {
178
     console.log('getCert token not defined, setting callback');
Chris Hines's avatar
Chris Hines committed
179
     console.log(this.token.value);
180
     this.token.pipe(skip(1),take(1)).subscribe(() => this.getCert());
181
182
     return
   }
Chris Hines's avatar
Chris Hines committed
183
   if (this.backendSelectionService.apiserver.value === null || this.backendSelectionService.apiserver.value === undefined) {
184
185
     console.log('getCert api server not defined, setting callback');
     this.backendSelectionService.apiserver.pipe(skip(1),take(1)).subscribe(() => this.getCert());
186
     return
Chris Hines's avatar
Chris Hines committed
187
   }
188

189

190
   console.log('generating ssh key');
Chris Hines's avatar
Chris Hines committed
191
192
193
194
195
   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();
196
   console.log('created a private key, getting it signed');
197

Chris Hines's avatar
Chris Hines committed
198
199
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
200

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

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

Chris Hines's avatar
Chris Hines committed
209
210
211
212
213
214
215
public getKeys(id?: Identity) {
  try{
    return JSON.parse(sessionStorage.getItem('keys'));
  } catch {
    return [];
  }
}
Chris Hines's avatar
Chris Hines committed
216
217

 makeKeyCert(key: string, resp, sshauthzservice: SshAuthzServer) {
218
219
220
   let keyCert = new KeyCert()
   keyCert.key = key;
   keyCert.cert = resp['cert'];
Chris Hines's avatar
Chris Hines committed
221
222
223
224
225
226
227
228
229
230
231
232
   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
233
234
235
236
237
   // 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);
   }
Chris Hines's avatar
Chris Hines committed
238
   let path=sessionStorage.getItem('path');
239
240
241
242
243
   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
244
245
246
247
248
249
250
 }

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

 public updateAgentContents() {
255
256
257
258
259
260
   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
261
262
263
   if (this.statusMsg !== undefined) {
    this.statusMsg.next("Updating the list of available accounts");
   };
Chris Hines's avatar
Chris Hines committed
264
265
266
267
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   var anyvar: any;
   this.http.get<any>(this.backendURI+'/sshagent',options)
268
                     .pipe(catchError(this.handleError(anyvar)))
Chris Hines's avatar
Chris Hines committed
269
                     .subscribe(resp => { this.agentContents.next(resp); this.statusMsg.next("") },
Chris Hines's avatar
Chris Hines committed
270
271
272
273
274
                                error => this.querySshAgentError(error));
                     // .subscribe(resp => this.computeSitesService.updateIdentities(resp),
                     //            error => this.httperror(error))
 }

275

Chris Hines's avatar
Chris Hines committed
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
303
304
305
306
307
 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))
308
309
 }

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

Chris Hines's avatar
Chris Hines committed
314
315
316
   sessionStorage.setItem('authservice', JSON.stringify([authservice,nonce]));
   console.log('stored the authservice in sessionStorage');
   console.log(sessionStorage.getItem('authservice'));
Chris Hines's avatar
Chris Hines committed
317
   sessionStorage.setItem('path', '/launch');
Chris Hines's avatar
Chris Hines committed
318
319
320
321
322
   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);
   }
323
324
 }

Chris Hines's avatar
Chris Hines committed
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
 // 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);
 //
 //   }

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

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