authorisation.service.ts 12.7 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
155
156
157
158

   //Verify that the state matched the nonce we used when initiating login
   let tuple = JSON.parse(localStorage.getItem('authservice'));
   if (tuple[1] != state) {
     return
   }

Chris Hines's avatar
Chris Hines committed
159
160
   this.token.next(new AuthToken(tokenmatch[1],tuple[0]));
   //this.token = new AuthToken(tokenmatch[1],tuple[0]);
161
162
163
164


 }

165
 public getCert() {
Chris Hines's avatar
Chris Hines committed
166
   var token: AuthToken;
167
168
169
170
171
   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
172
173
   if (token.token === undefined || token.token === '' || token.token == null) {
     this.token.pipe(take(1)).subscribe(() => this.getCert())
174
175
176
177
178
     return
   }
   if (this.backendSelectionService.apiserver.value === null || this.backendSelectionService.apiserver.value == undefined) {
     this.backendSelectionService.apiserver.pipe(skip(1),take(1)).subscribe(() => this.getCert())
     return
Chris Hines's avatar
Chris Hines committed
179
   }
180

181

Chris Hines's avatar
Chris Hines committed
182
183
184
185
186
   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();
187

Chris Hines's avatar
Chris Hines committed
188
189
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
190

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

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

Chris Hines's avatar
Chris Hines committed
199
200
201
202
203
204
205
public getKeys(id?: Identity) {
  try{
    return JSON.parse(sessionStorage.getItem('keys'));
  } catch {
    return [];
  }
}
Chris Hines's avatar
Chris Hines committed
206
207

 makeKeyCert(key: string, resp, sshauthzservice: SshAuthzServer) {
208
209
210
   let keyCert = new KeyCert()
   keyCert.key = key;
   keyCert.cert = resp['cert'];
Chris Hines's avatar
Chris Hines committed
211
212
213
214
215
216
217
218
219
220
221
222
   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
223
224
225
226
227
   // 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);
   }
228
   let path=localStorage.getItem('path');
229
230
231
232
233
   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
234
235
236
237
238
239
240
 }

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

 public updateAgentContents() {
245
246
247
248
249
250
   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
251
252
253
   if (this.statusMsg !== undefined) {
    this.statusMsg.next("Updating the list of available accounts");
   };
Chris Hines's avatar
Chris Hines committed
254
255
256
257
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   var anyvar: any;
   this.http.get<any>(this.backendURI+'/sshagent',options)
258
                     .pipe(catchError(this.handleError(anyvar)))
Chris Hines's avatar
Chris Hines committed
259
                     .subscribe(resp => { this.agentContents.next(resp); this.statusMsg.next("") },
Chris Hines's avatar
Chris Hines committed
260
261
262
263
264
                                error => this.querySshAgentError(error));
                     // .subscribe(resp => this.computeSitesService.updateIdentities(resp),
                     //            error => this.httperror(error))
 }

265

Chris Hines's avatar
Chris Hines committed
266
267
268
269
270
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
 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))
298
299
 }

Chris Hines's avatar
Chris Hines committed
300
 public login(authservice: SshAuthzServer) {
301
   let redirect_uri = window.location.origin+this.locationStrategy.getBaseHref()+"sshauthz_callback";
302
   let nonce=Math.random().toString(36).substring(2, 15)
Chris Hines's avatar
Chris Hines committed
303
304
305



306
   localStorage.setItem('authservice', JSON.stringify([authservice,nonce]));
Chris Hines's avatar
Chris Hines committed
307
   localStorage.setItem('path', this.location.path());
Chris Hines's avatar
Chris Hines committed
308
309
310
311
312
   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);
   }
313
314
 }

Chris Hines's avatar
Chris Hines committed
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
 // 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);
 //
 //   }

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

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