authorisation.service.ts 9.47 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, switchMap } from 'rxjs/operators';
Chris Hines's avatar
Chris Hines committed
5
import {LocationStrategy, Location} from '@angular/common';
6
7
8
// import { keypair } from 'keypair';
import * as keypair from 'keypair';
import * as forge from "node-forge";
Chris Hines's avatar
Chris Hines committed
9
import { Identity, AuthToken, KeyCert, SshAuthzServer } from './identity';
10
import { APIServer } from './apiserver';
Chris Hines's avatar
Chris Hines committed
11
import {BackendSelectionService} from './backend-selection.service';
12
import { throwError, of, combineLatest } from 'rxjs';
13
import {NotificationsService} from './notifications.service';
Chris Hines's avatar
Chris Hines committed
14
15
16


export class SshauthzServer {}
17
18
19
20
21

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


  constructor(private http: HttpClient,
              private locationStrategy: LocationStrategy,
Chris Hines's avatar
Chris Hines committed
37
              private backendSelectionService: BackendSelectionService,
38
39
              private location: Location, 
              private notifications: NotificationsService) {
40
                this.sshAuthzServers = new BehaviorSubject<SshAuthzServer[]>([]);
41
                this.loggedInAuthZ = new BehaviorSubject<SshAuthzServer[]>(null);
42
                this.loggedOutAuthZ = new BehaviorSubject<SshAuthzServer[]>([]);
43
                this.backendURI = null;
Chris Hines's avatar
Chris Hines committed
44
                this.agentContents = new BehaviorSubject(null);
Chris Hines's avatar
Chris Hines committed
45
                this.keys = [];
Chris Hines's avatar
Chris Hines committed
46
                this.getSshAuthzServers();
Chris Hines's avatar
Chris Hines committed
47
                this.keys = [];
48
                
49
50
51
52
53
54
55
56
    this.backendSelectionService.apiserver.pipe(
      filter((v) => v !== null && v !== undefined),
    )
      .subscribe((value) => { this.backendURI = value.tes }); // Once we have a value for backend, store that value locally

    // Once we have backend server, check what our ssh agent has in it
    this.backendSelectionService.apiserver.pipe(
      filter((v) => v !== null && v !== undefined),
57
58
      switchMap((v) => this.updateAgentContents(v.tes)),
    ).subscribe((_) => { return }, (err) => console.error(err));  // An empty subscription is necessary for the observables to fire
59
60
61


    /* Once we have a value for agent Conents, and a list of servers, we can figure out which ones we hav elogged into and which ones we haven't */
62
63
64
65
66
67

    var authZ$: Observable<any>;
    authZ$ = combineLatest([this.agentContents.pipe(filter((v) => v !== null && v !== undefined)),this.sshAuthzServers.pipe(filter((v) => v !== null && v !== undefined))]).pipe(
      map(([agentContents,authzServers]) => { return this.updateLoggedAuthZ(agentContents,authzServers)}),
      catchError((e) => { console.error('errort getting values for logged in and out',e); return throwError(e) })
    );
Chris Hines's avatar
Chris Hines committed
68
    authZ$.subscribe(([loggedin,loggedout]) => { this.loggedInAuthZ.next(loggedin); this.loggedOutAuthZ.next(loggedout);  })
69
70
    this.loggedInAuthZ$ = authZ$[0];
    this.loggedOutAuthZ$ = authZ$[1];
Chris Hines's avatar
Chris Hines committed
71
72
 }

Chris Hines's avatar
Chris Hines committed
73

74
75
76
77
78
79
80
 storeLocalAuthZ(authz: any) {
   try {
     localStorage.setItem('localauthservers',JSON.stringify(authz));
   } catch {
   }
   this.getSshAuthzServers();
 }
Chris Hines's avatar
Chris Hines committed
81

82
83
84
85
86
 removeLocalAuthZ() {
   localStorage.removeItem('localauthservers');
   this.getSshAuthzServers();
 }

Chris Hines's avatar
Chris Hines committed
87
88
89
90
91
92
 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));
93
 }
Chris Hines's avatar
Chris Hines committed
94

95
 private updateLoggedAuthZ(agentContents,authzServers) {
96
97
98
     let loggedin = []
     let loggedout = []
     var found: boolean;
Chris Hines's avatar
Chris Hines committed
99
100
101
     if (agentContents == null) {
       return
     }
102
103
104
105
106
     for (let s of authzServers) {
         found=false;
         for (let cert of agentContents) {
           if ('Signing CA' in cert) {
             for (let ca of cert['Signing CA']) {
Chris Hines's avatar
Chris Hines committed
107
               if (ca.indexOf(s.cafingerprint) != -1) {
108
109
110
111
112
113
114
115
116
117
118
119
120
121
                 loggedin.push(s)
                 found=true;
                 continue;
               }
             }
           }
           if (found) {
               continue;
           }
         }
         if (!found)  {
           loggedout.push(s)
         }
     }
122
     return [loggedin, loggedout]
123
124
125
 }
        

Chris Hines's avatar
Chris Hines committed
126
 updateSshAuthzServers(resp) {
127
128
129
130
131
   var auths: SshAuthzServer[];
   var localauths: SshAuthzServer[] = [];
   var server: SshAuthzServer;
   auths = <SshAuthzServer[]>resp;
   try {
Chris Hines's avatar
Chris Hines committed
132
       localauths = JSON.parse(localStorage.getItem('localauthservers'))
133
134
135
   } catch {
       localauths = []
   }
Chris Hines's avatar
Chris Hines committed
136
137
138
139
   if (localauths !== null) {
       for (server of localauths) {
           auths.push(server);
       }
140
141
   }
   this.sshAuthzServers.next(auths);
Chris Hines's avatar
Chris Hines committed
142
143
 }

Chris Hines's avatar
Chris Hines committed
144
145
146
147
148
149
150
public getKeys(id?: Identity) {
  try{
    return JSON.parse(sessionStorage.getItem('keys'));
  } catch {
    return [];
  }
}
Chris Hines's avatar
Chris Hines committed
151

152
    /* makeKeyCert(key: string, resp, sshauthzservice: SshAuthzServer) {
153
154
155
   let keyCert = new KeyCert()
   keyCert.key = key;
   keyCert.cert = resp['cert'];
Chris Hines's avatar
Chris Hines committed
156
157
158
159
160
161
162
163
164
165
166
   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
167
168
169
170
171
   // 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
172
   let path=sessionStorage.getItem('path');
Chris Hines's avatar
Chris Hines committed
173
174
   //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])});
Chris Hines's avatar
Chris Hines committed
175
   this.sshAdd(keyCert);
176
   // only navigate once the agent contents has been refreshed
177
 }*/
Chris Hines's avatar
Chris Hines committed
178
179
180
181

 public querySshAgentError(error: any) {
   this.agentContents.next([]);
   if (error.status == 0) {
182
     this.notifications.notify("A network error occured. Are you connected to the internet?")
Chris Hines's avatar
Chris Hines committed
183
   }
184
   this.notifications.notify("Error querying ssh agent");
Chris Hines's avatar
Chris Hines committed
185
186
 }

187
188
189
190
191
192
193
194
195
196
  public updateAgentContents(apiserver?: string): Observable<any> {
    /* Query ssh agent running on the apiserver 
     * Tap the even stream to update the notifications 
     */
   if (apiserver === undefined) {
     if (this.backendURI == null) {
       throwError('no backend to query');
     } else {
       apiserver = this.backendURI
     }
197
   }
198

Chris Hines's avatar
Chris Hines committed
199
200
201
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   var anyvar: any;
202
203
   let agentquery$ = this.http.get<any>(apiserver+'/sshagent',options)
   let agentpipe$ = agentquery$.pipe(
204
     tap((_) => this.notifications.notify("Updating the list of available accounts")),
205
     catchError((e) => { this.querySshAgentError(e); return of([])}),
206
207
208
209
210
211
212
213
214
215
216
217
     tap((resp) => { console.log('agent contents',resp) }),
     tap((resp) => { console.log('agent contents',resp) }),
     tap((resp) => { 
       if (this.agentContents.value !== null && this.agentContents.value.length > resp.length) {
         this.notifications.notify("Your login expired. Please login again");
         console.log('login expired');
       } else {
         this.notifications.notify("");
       };
       this.agentContents.next(resp)
     }),
     //tap((_) => this.notifications.notify(""))
218
219
   )
   return agentpipe$
Chris Hines's avatar
Chris Hines committed
220
221
 }

222

Chris Hines's avatar
Chris Hines committed
223
 private killAgent() {
224
   this.notifications.notify("Logging out")
Chris Hines's avatar
Chris Hines committed
225
226
227
228
   let headers = new HttpHeaders();
   let options = { headers: headers, withCredentials: true};
   var anyvar: any;
   this.http.delete<any>(this.backendURI+'/sshagent',options)
229
230
231
232
     .pipe(
       catchError(this.handleError(anyvar)),
       switchMap((v) => this.updateAgentContents()))
     .subscribe((v) => {}, (error) => this.httperror(error));
Chris Hines's avatar
Chris Hines committed
233
234
235
236
237
238
239
 }

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

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

Chris Hines's avatar
Chris Hines committed
244
   sessionStorage.setItem('authservice', JSON.stringify([authservice,nonce]));
Chris Hines's avatar
Chris Hines committed
245
   sessionStorage.setItem('path', '/launch');
Chris Hines's avatar
Chris Hines committed
246
247
248
249
250
   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);
   }
251
252
253
 }

 private httperror(error: any) {
254
   this.notifications.notify('There was an error logging in or generating crypto tokens');
255
   console.error(error);
256
257
 }

Chris Hines's avatar
Chris Hines committed
258
 private handleError<T> (result?: T) {
259
   return (error: any): Observable<T> => {
Chris Hines's avatar
Chris Hines committed
260
     if (error.status == 500) {
Chris Hines's avatar
Chris Hines committed
261
       return throwError("The backend server encountered and error. Please try again in a few minutes")
Chris Hines's avatar
Chris Hines committed
262
263
264
     }
     return throwError(error.message);
     // return of(result as T);
265
266
267
   };
 }
}