diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8c6a42bc10b6555988f8c2c7f23d1d5e9c34cd07..355c7703544b2af298c763783497ad10fb367d5f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { TesService } from './tes.service'; +import { AuthorisationService} from './authorisation.service'; @Component({ selector: 'app-root', @@ -13,7 +14,7 @@ export class AppComponent { public authorised: boolean; // private testingAuth: boolean; - constructor(private tesService: TesService,) { + constructor(private tesService: TesService, private authService: AuthorisationService) { }; ngOnInit() { @@ -21,13 +22,13 @@ export class AppComponent { // this.testingAuth = false; } - login() { - console.log("login"); - this.tesService.login() - } - - logout() { - console.log("logout"); - this.tesService.logout(); - } + // login() { + // console.log("login"); + // this.authService.login() + // } + // + // logout() { + // console.log("logout"); + // this.tesService.logout(); + // } } diff --git a/src/app/authorisation.service.spec.ts b/src/app/authorisation.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3286e3a92786fb5b78966b2861c759a2e8d33e3e --- /dev/null +++ b/src/app/authorisation.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { AuthorisationService } from './authorisation.service'; + +describe('AuthorisationService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthorisationService] + }); + }); + + it('should be created', inject([AuthorisationService], (service: AuthorisationService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/authorisation.service.ts b/src/app/authorisation.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..017e383419c7478b7872123a0c135059a442abd4 --- /dev/null +++ b/src/app/authorisation.service.ts @@ -0,0 +1,125 @@ +import { Injectable } from '@angular/core'; +import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import { catchError, map, tap } from 'rxjs/operators'; +import { ActivatedRoute, Router } from '@angular/router'; +import {LocationStrategy} from '@angular/common'; +// import { keypair } from 'keypair'; +import * as keypair from 'keypair'; +import * as forge from "node-forge"; +import { Identity, AuthToken, KeyCert, AuthService } from './identity'; +import {BehaviorSubject} from 'rxjs/BehaviorSubject'; +import {Subject} from 'rxjs/Subject'; +import {TesService} from './tes.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthorisationService { + private token: Subject<AuthToken>; + // private keyCert: Subject<KeyCert>; + + + constructor(private http: HttpClient, + private locationStrategy: LocationStrategy, + private route: ActivatedRoute, + private router: Router, + private tesService: TesService) { + this.token = new Subject<AuthToken>(); + this.token.subscribe(token => this.getCert(token)); + this.route.fragment.subscribe(frag => this.storeToken(frag)); + } + storeToken(frag: string) { + if (frag === undefined || frag == null) { + return; + } + let tokenmatch = null; + let statematch = null; + if (!(frag === undefined) && !(frag == null)) { + tokenmatch = frag.match(/access_token\=([\S\s]*?)[&|$]/); + statematch = frag.match(/state\=([\S\s]*?)[&|$]/); + } + if (tokenmatch == null || statematch == null) { + return; + } + + let accesstoken = tokenmatch[1]; + let state = statematch[1]; + this.router.navigate(['/']); + + //Verify that the state matched the nonce we used when initiating login + let tuple = JSON.parse(localStorage.getItem('authservice')); + if (tuple[1] != state) { + return + } + + this.token.next(new AuthToken(tokenmatch[1],tuple[0])); + + // TODO fire off a query to the auth service to get the associated sites + + + } + + getCert(token: AuthToken) { + console.log('in getCert'); + if (token.token === undefined || token.token === '' || token.token == null) { + console.log('no authtoken available, we wont be able to generate a cert'); + console.log(token); + return + } + console.log("Generating key matter"); + + 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(); + console.log("generating new keys took", endtime.valueOf() - starttime.valueOf()) + + let headers = new HttpHeaders(); + let options = { headers: headers, withCredentials: true}; + + let data = {'token': token.token, 'pubkey': sshpub, 'signing_url': token.authservice.sign}; + + console.log('posting to getcert',this.tesService.Base); + this.http.post<any>(this.tesService.Base+'/getcert',data, options) + .pipe(catchError(this.handleError('getCert',[]))) + .subscribe(resp => this.makeKeyCert(newkeypair.private, resp, token.authservice)) + console.log('getcert complete'); + } + + makeKeyCert(key: string, resp, authservice: AuthService) { + let keyCert = new KeyCert() + keyCert.key = key; + keyCert.cert = resp['cert']; + keyCert.authservice = authservice; + console.log('updating keycert',keyCert); + this.tesService.keyCert.next(keyCert); + } + + public login() { + let redirect_uri = window.location.origin+this.locationStrategy.getBaseHref()+"sshauthz_callback"; + let nonce="asdfzxcv"; + let authservice = new AuthService(); + authservice.base = "https://autht.massive.org.au/hpcid/"; + authservice.authorise = authservice.base + 'oauth/authorize'; + authservice.sign = authservice.base + 'api/v1/sign_key'; + authservice.client_id = "86c06039-e589-4b39-9d1f-9eca431be18f"; + localStorage.setItem('authservice', JSON.stringify([authservice,nonce])); + window.location.assign(authservice.authorise+"?response_type=token&redirect_uri="+redirect_uri+"&state="+nonce+"&client_id="+authservice.client_id); + } + + private httperror(error: any) { + console.log(error); + } + + private handleError<T> (operation = 'operation', result?: T) { + return (error: any): Observable<T> => { + console.log('in handle error',operation); + console.log(error.status); + console.error(error); + return of(result as T); + }; + } +} diff --git a/src/app/logindialog/logindialog.component.ts b/src/app/logindialog/logindialog.component.ts index b9617093d4d66b66aec901417ad982ae2090145a..28bf80fa0eda1a5094efe40e0e003d881f0ff789 100644 --- a/src/app/logindialog/logindialog.component.ts +++ b/src/app/logindialog/logindialog.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, Inject } from '@angular/core'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material'; import { TesService } from '../tes.service'; +import {AuthorisationService} from '../authorisation.service'; @@ -14,14 +15,15 @@ export class LogindialogComponent implements OnInit { constructor( public dialogRef: MatDialogRef<LogindialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any, - private tesService: TesService) { + private tesService: TesService, + private authService: AuthorisationService ) { } ngOnInit() { } onLogin() { - this.tesService.login(); + this.authService.login(); } onCancel() { this.dialogRef.close(); diff --git a/src/app/tes.service.ts b/src/app/tes.service.ts index 0362deac03878a7a24207a3e3c30e82bc927807e..fd9b487261dcaa965e122480534f5889724b5e7f 100644 --- a/src/app/tes.service.ts +++ b/src/app/tes.service.ts @@ -6,7 +6,6 @@ import { catchError, map, tap } from 'rxjs/operators'; import { Job } from './job'; import {BehaviorSubject} from 'rxjs/BehaviorSubject'; import {Subject} from 'rxjs/Subject'; - import { Strudelapp } from './strudelapp'; import { Computesite } from './computesite'; import { Identity, AuthToken, KeyCert, AuthService } from './identity'; @@ -16,95 +15,57 @@ import { StrudelappsService } from './strudelapps.service'; import { timer } from 'rxjs/observable/timer'; import { repeat } from 'rxjs/operators'; import {LocationStrategy} from '@angular/common'; -// import { keypair } from 'keypair'; -import * as keypair from 'keypair'; -import * as forge from "node-forge"; import { ActivatedRoute, Router } from '@angular/router'; -// import keypair = require('keypair'); -// import forge = require('node-forge'); - - @Injectable({ providedIn: 'root', }) export class TesService { -// private Base='http://localhost:5000' -private Base='https://vm-118-138-240-255.erc.monash.edu.au/tes' -// public authorised: BehaviorSubject<boolean>; -// public tesSelected: BehaviorSubject<boolean>; +public Base='https://vm-118-138-240-255.erc.monash.edu.au/tes' public statusMsg: BehaviorSubject<any>; public jobs: any[]; public busy: BehaviorSubject<boolean> ; -// public testingAuth: BehaviorSubject<boolean>; public joblist: BehaviorSubject<{ [id: string ]: Job[]}>; private timerSubscription: any; -private token: Subject<AuthToken>; -private keyCert: Subject<KeyCert>; -private signing_endpoint: string; -private sshauthzbase: string; -private sshauthz: string; -private client_id: string; -private keypair: any; -private ssh: any; -private sshcert: any; +public keyCert: Subject<KeyCert>; public identities: BehaviorSubject<Identity[]>; private batchinterface: {[id: string] : BatchInterface}; - - constructor(private http: HttpClient, - private locationStrategy: LocationStrategy, - private route: ActivatedRoute, - private router: Router, private computesite: ComputesitesService, private strudelappsService: StrudelappsService) { this.statusMsg = new BehaviorSubject<any>(''); this.busy = new BehaviorSubject<boolean>(false); this.joblist = new BehaviorSubject<{[id: string]: Job[]}>({}); this.timerSubscription = null; - this.token = new Subject<AuthToken>(); this.keyCert = new Subject<KeyCert>(); this.identities= new BehaviorSubject<Identity[]>([]); - this.route.fragment.subscribe(frag => this.storeToken(frag)); - this.token.subscribe(token => this.getCert(token)); this.keyCert.subscribe(keyCert => this.sshAdd(keyCert)); this.batchinterface = {}; this.getIdentities(); } - - updateJoblist(resp, identity: Identity) { - let joblist = <Job[]>resp; - let alljobs = this.joblist.value; - let i = 0; - for (let j of joblist) { - j.app = this.strudelappsService.getApp(j.name); - j.identity = identity; - } - alljobs[identity.repr()] = joblist; - this.joblist.next(alljobs); - this.statusMsg.next(null); - } - - submitted(resp: any ) { - this.busy.next(false); - this.statusMsg.next('Updating job list'); - this.getJobs(); - } - - buildParams(app: Strudelapp, identity: Identity, batchinterface: BatchInterface): string { + private buildParams(app: Strudelapp, identity: Identity, batchinterface: BatchInterface): string { let params = new URLSearchParams(); - let appstr = JSON.stringify(app); - params.set('app',appstr); - let interfacestr = JSON.stringify(batchinterface); - params.set('interface',interfacestr); - let identitystr = JSON.stringify(identity); - params.set('identity',identitystr); - + params.set('app',JSON.stringify(app)); + params.set('interface',JSON.stringify(batchinterface)); + params.set('identity',JSON.stringify(identity)); return params.toString(); } + updateJoblist(resp, identity: Identity) { + let joblist = <Job[]>resp; + let alljobs = this.joblist.value; + let i = 0; + for (let j of joblist) { + j.app = this.strudelappsService.getApp(j.name); + j.identity = identity; + } + alljobs[identity.repr()] = joblist; + this.joblist.next(alljobs); + this.statusMsg.next(null); + } + getJobs() { let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; @@ -134,16 +95,28 @@ private batchinterface: {[id: string] : BatchInterface}; .pipe(catchError(this.handleError('getJobs',[]))) .subscribe(resp => this.updateJoblist(resp,identity)); } + } + + private startPolling() { + this.statusMsg.next(null); + if (!(this.timerSubscription === null)) { + this.timerSubscription.unsubscribe() + } + this.timerSubscription = timer(5000).pipe(repeat()).subscribe(() => this.getJobs()); + this.getJobs(); + } + private stopPolling() { + if (!(this.timerSubscription === null)) { + this.timerSubscription.unsubscribe() + } } - getconfig(app: Strudelapp, identity: Identity): Observable<any> { + public getconfig(app: Strudelapp, identity: Identity): Observable<any> { let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; return this.http.get<any>(identity.site.url+'getconfig/'+app.name,options) .pipe(catchError(this.handleError('getconfig',[]))) - - } submit(app: Strudelapp, identity: Identity, batchinterface: BatchInterface) { @@ -157,6 +130,12 @@ private batchinterface: {[id: string] : BatchInterface}; .subscribe(resp => this.busy.next(false)); } + submitted(resp: any ) { + this.busy.next(false); + this.statusMsg.next('Updating job list'); + this.getJobs(); + } + cancel(job: Job) { console.log("In tes cancel"); let headers = new HttpHeaders(); @@ -183,80 +162,7 @@ private batchinterface: {[id: string] : BatchInterface}; this.busy.next(false); } - -storeToken(frag: string) { - if (frag === undefined || frag == null) { - return; - } - let tokenmatch = null; - let statematch = null; - if (!(frag === undefined) && !(frag == null)) { - tokenmatch = frag.match(/access_token\=([\S\s]*?)[&|$]/); - statematch = frag.match(/state\=([\S\s]*?)[&|$]/); - } - if (tokenmatch == null || statematch == null) { - return; - } - - let accesstoken = tokenmatch[1]; - let state = statematch[1]; - this.router.navigate(['/']); - - //Verify that the state matched the nonce we used when initiating login - let tuple = JSON.parse(localStorage.getItem('authservice')); - if (tuple[1] != state) { - return - } - - this.token.next(new AuthToken(tokenmatch[1],tuple[0])); - - // TODO fire off a query to the auth service to get the associated sites - - -} - -getCert(token: AuthToken) { - console.log('in getCert'); - if (token.token === undefined || token.token === '' || token.token == null) { - console.log('no authtoken available, we wont be able to generate a cert'); - console.log(token); - return - } - console.log("Generating key matter"); - - 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(); - console.log("generating new keys took", endtime.valueOf() - starttime.valueOf()) - - let headers = new HttpHeaders(); - let options = { headers: headers, withCredentials: true}; - - let data = {'token': token.token, 'pubkey': sshpub, 'signing_url': token.authservice.sign}; - - this.busy.next(true); - this.statusMsg.next("Generating Certificates ...") - console.log('posting to getcert',this.Base); - this.http.post<any>(this.Base+'/getcert',data, options) - .pipe(catchError(this.handleError('getCert',[]))) - .subscribe(resp => this.makeKeyCert(newkeypair.private, resp, token.authservice)) - console.log('getcert complete'); -} - -makeKeyCert(key: string, resp, authservice: AuthService) { - let keyCert = new KeyCert() - keyCert.key = key; - keyCert.cert = resp['cert']; - keyCert.authservice = authservice; - console.log('updating keycert',keyCert); - this.keyCert.next(keyCert); -} - private sshAdd(keyCert: KeyCert) { - - console.log('in sshAdd'); if (keyCert.key == undefined) { return; } @@ -265,13 +171,10 @@ private sshAdd(keyCert: KeyCert) { this.statusMsg.next("Authorising ...") let data = {'key': keyCert.key, 'cert': keyCert.cert}; - console.log('adding key',data); - this.http.post<any>(this.Base+'/sshagent',data,options) .pipe(catchError(this.handleError('storeCert',[]))) .subscribe(resp => this.getIdentities(), error => this.httperror(error)); - console.log('sshAdd complete'); } private getIdentities() { @@ -282,12 +185,10 @@ private getIdentities() { this.http.get<any>(this.Base+'/sshagent',options) .pipe(catchError(this.handleError('getIdentities',[]))) .subscribe(resp => this.updateIdentities(resp)); - console.log('getIdentities complete'); } private killAgent() { this.statusMsg.next("Updating the list of available accounts") - this let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; this.http.delete<any>(this.Base+'/sshagent',options) @@ -298,11 +199,8 @@ private killAgent() { private updateIdentities(resp) { //TODO Each cert as the signing CA parameter. Use this to find the compute sites // rather than just assuming sites[0] - console.log('attempting to update our local list of identities'); let certcontents = resp; - console.log(certcontents); let identities = []; - console.log('identities updated'); let sites = this.computesite.getComputeSites(); let idsShort = [] for (let i in certcontents) { @@ -311,52 +209,17 @@ private updateIdentities(resp) { this.identities.next(identities); if (identities.length == 0) { this.statusMsg.next(null); - console.log('local identities, none available'); return; } - console.log('local identities updated'); this.startPolling(); } -private startPolling() { - console.log('in start polling'); - this.statusMsg.next(null); - if (!(this.timerSubscription === null)) { - console.log('unsubscribing timer'); - this.timerSubscription.unsubscribe() - } - console.log('creating timer to poll for jobs'); - // this.timerSubscription = timer(100,5000).subscribe(() => this.getJobs()); - - this.timerSubscription = timer(5000).pipe(repeat()).subscribe(() => this.getJobs()); - this.getJobs(); - console.log('start polling complete'); -} - -public login() { - let redirect_uri = window.location.origin+this.locationStrategy.getBaseHref()+"sshauthz_callback"; - let nonce="asdfzxcv"; - let authservice = new AuthService(); - authservice.base = "https://autht.massive.org.au/hpcid/"; - authservice.authorise = authservice.base + 'oauth/authorize'; - authservice.sign = authservice.base + 'api/v1/sign_key'; - authservice.client_id = "86c06039-e589-4b39-9d1f-9eca431be18f"; - localStorage.setItem('authservice', JSON.stringify([authservice,nonce])); - window.location.assign(authservice.authorise+"?response_type=token&redirect_uri="+redirect_uri+"&state="+nonce+"&client_id="+authservice.client_id); -} - public logout(): Boolean { - this.token = null; - this.sshcert = null; this.statusMsg.next(null); this.killAgent(); - // if (!(this.timerSubscription === null)) { - // this.timerSubscription.unsubscribe() - // } return true; } - public getstatusMsgSubject(): BehaviorSubject<any> { return this.statusMsg; }