authorisation.service.ts 12.6 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;
Chris Hines's avatar
Chris Hines committed
52
                this.agentContents = new BehaviorSubject(null);
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
    this.backendSelectionService.apiserver.pipe(filter((v) => v !== null && v !== undefined))
Chris Hines's avatar
Chris Hines committed
57
      .subscribe((value) => { this.backendURI = value.tes ; this.agentContents.next(null) })
58
59
    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
 private updateLoggedAuthZ() {
     let agentContents = this.agentContents.value
     let authzServers = this.sshAuthzServers.value
     let loggedin = []
     let loggedout = []
     var found: boolean;
Chris Hines's avatar
Chris Hines committed
92
93
94
     if (agentContents == null) {
       return
     }
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
     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
120
 updateSshAuthzServers(resp) {
121
122
123
124
125
126
127
128
129
   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
130
131
132
133
   if (localauths !== null) {
       for (server of localauths) {
           auths.push(server);
       }
134
135
   }
   this.sshAuthzServers.next(auths);
Chris Hines's avatar
Chris Hines committed
136
137
 }

138
139
140
141
142
143
144
 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
145
146
     tokenmatch = frag.match(/access_token\=([^&]+)(&|$)/);
     statematch = frag.match(/state\=([^&]+)(&|$)/);
147
148
149
150
151
152
153
   }
   if (tokenmatch == null || statematch == null) {
     return;
   }

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

   //Verify that the state matched the nonce we used when initiating login
Chris Hines's avatar
Chris Hines committed
157
   let tuple = JSON.parse(sessionStorage.getItem('authservice'));
158
159
160
161
   if (tuple[1] != state) {
     return
   }

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


 }

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

180

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

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

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

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

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

 makeKeyCert(key: string, resp, sshauthzservice: SshAuthzServer) {
207
208
209
   let keyCert = new KeyCert()
   keyCert.key = key;
   keyCert.cert = resp['cert'];
Chris Hines's avatar
Chris Hines committed
210
211
212
213
214
215
216
217
218
219
220
   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))
Chris Hines's avatar
Chris Hines committed
221
222
223
224
225
   // 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
226
   let path=sessionStorage.getItem('path');
Chris Hines's avatar
Chris Hines committed
227
228
   //skip1 because loggedInAuthZ is a behaviour subject and we don't want the current value but the value
   this.loggedInAuthZ.pipe(skip(1),take(1)).subscribe( () => {this.readyToNavigate.next([true,path])});
229
   console.log('cert generated, adding keycert to agent');
Chris Hines's avatar
Chris Hines committed
230
   this.sshAdd(keyCert);
231
   // only navigate once the agent contents has been refreshed
Chris Hines's avatar
Chris Hines committed
232
233
234
235
 }

 public querySshAgentError(error: any) {
   this.agentContents.next([]);
236
237
   console.log('querySshAgentError');
   console.log(error);
Chris Hines's avatar
Chris Hines committed
238
239
240
   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
   if (this.backendURI == null) {
     return
   }
Chris Hines's avatar
Chris Hines committed
248
249
250
   if (this.statusMsg !== undefined) {
    this.statusMsg.next("Updating the list of available accounts");
   };
Chris Hines's avatar
Chris Hines committed
251
252
253
254
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   var anyvar: any;
   this.http.get<any>(this.backendURI+'/sshagent',options)
255
                     .pipe(catchError(this.handleError(anyvar)))
256
                     .subscribe(resp => { this.agentContents.next(resp); console.log('agent response'); console.log(resp); this.statusMsg.next("") },
Chris Hines's avatar
Chris Hines committed
257
258
259
260
261
                                error => this.querySshAgentError(error));
                     // .subscribe(resp => this.computeSitesService.updateIdentities(resp),
                     //            error => this.httperror(error))
 }

262

Chris Hines's avatar
Chris Hines committed
263
264
265
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
 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))
295
296
 }

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

Chris Hines's avatar
Chris Hines committed
301
   sessionStorage.setItem('authservice', JSON.stringify([authservice,nonce]));
Chris Hines's avatar
Chris Hines committed
302
   sessionStorage.setItem('path', '/launch');
Chris Hines's avatar
Chris Hines committed
303
304
305
306
307
   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);
   }
308
309
 }

Chris Hines's avatar
Chris Hines committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
 // 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);
 //
 //   }

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

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