| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  | // Imports
 | 
					
						
							|  |  |  | import * as jwt from 'jsonwebtoken'; | 
					
						
							| 
									
										
										
										
											2017-03-07 11:39:37 +02:00
										 |  |  | import {GithubPullRequests} from '../common/github-pull-requests'; | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  | import {GithubTeams} from '../common/github-teams'; | 
					
						
							|  |  |  | import {assertNotMissingOrEmpty} from '../common/utils'; | 
					
						
							|  |  |  | import {UploadError} from './upload-error'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Interfaces - Types
 | 
					
						
							|  |  |  | interface JwtPayload { | 
					
						
							|  |  |  |   slug: string; | 
					
						
							|  |  |  |   'pull-request': number; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Classes
 | 
					
						
							|  |  |  | export class BuildVerifier { | 
					
						
							|  |  |  |   // Properties - Protected
 | 
					
						
							|  |  |  |   protected githubPullRequests: GithubPullRequests; | 
					
						
							|  |  |  |   protected githubTeams: GithubTeams; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Constructor
 | 
					
						
							|  |  |  |   constructor(protected secret: string, githubToken: string, protected repoSlug: string, organization: string, | 
					
						
							|  |  |  |               protected allowedTeamSlugs: string[]) { | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('secret', secret); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('githubToken', githubToken); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('repoSlug', repoSlug); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('organization', organization); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('allowedTeamSlugs', allowedTeamSlugs && allowedTeamSlugs.join('')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.githubPullRequests = new GithubPullRequests(githubToken, repoSlug); | 
					
						
							|  |  |  |     this.githubTeams = new GithubTeams(githubToken, organization); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Methods - Public
 | 
					
						
							| 
									
										
										
										
											2017-03-07 11:39:37 +02:00
										 |  |  |   public getPrAuthorTeamMembership(pr: number): Promise<{author: string, isMember: boolean}> { | 
					
						
							|  |  |  |     return Promise.resolve(). | 
					
						
							|  |  |  |       then(() => this.githubPullRequests.fetch(pr)). | 
					
						
							|  |  |  |       then(prInfo => prInfo.user.login). | 
					
						
							|  |  |  |       then(author => this.githubTeams.isMemberBySlug(author, this.allowedTeamSlugs). | 
					
						
							|  |  |  |         then(isMember => ({author, isMember}))); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public verify(expectedPr: number, authHeader: string): Promise<void> { | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  |     return Promise.resolve(). | 
					
						
							|  |  |  |       then(() => this.extractJwtString(authHeader)). | 
					
						
							| 
									
										
										
										
											2017-03-07 11:39:37 +02:00
										 |  |  |       then(jwtString => this.verifyJwt(expectedPr, jwtString)). | 
					
						
							|  |  |  |       then(jwtPayload => this.verifyPr(jwtPayload['pull-request'])). | 
					
						
							|  |  |  |       catch(err => { throw new UploadError(403, `Error while verifying upload for PR ${expectedPr}: ${err}`); }); | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Methods - Protected
 | 
					
						
							|  |  |  |   protected extractJwtString(input: string): string { | 
					
						
							|  |  |  |     return input.replace(/^token +/i, ''); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-07 11:39:37 +02:00
										 |  |  |   protected verifyJwt(expectedPr: number, token: string): Promise<JwtPayload> { | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  |     return new Promise((resolve, reject) => { | 
					
						
							| 
									
										
										
										
											2017-06-17 21:03:10 +03:00
										 |  |  |       jwt.verify(token, this.secret, {issuer: 'Travis CI, GmbH'}, (err, payload: JwtPayload) => { | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  |         if (err) { | 
					
						
							|  |  |  |           reject(err.message || err); | 
					
						
							|  |  |  |         } else if (payload.slug !== this.repoSlug) { | 
					
						
							|  |  |  |           reject(`jwt slug invalid. expected: ${this.repoSlug}`); | 
					
						
							| 
									
										
										
										
											2017-03-07 11:39:37 +02:00
										 |  |  |         } else if (payload['pull-request'] !== expectedPr) { | 
					
						
							|  |  |  |           reject(`jwt pull-request invalid. expected: ${expectedPr}`); | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  |         } else { | 
					
						
							|  |  |  |           resolve(payload); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-07 11:39:37 +02:00
										 |  |  |   protected verifyPr(pr: number): Promise<void> { | 
					
						
							|  |  |  |     return this.getPrAuthorTeamMembership(pr). | 
					
						
							|  |  |  |       then(({author, isMember}) => isMember ? Promise.resolve() : Promise.reject( | 
					
						
							|  |  |  |         `User '${author}' is not an active member of any of the following teams: ` + | 
					
						
							|  |  |  |         `${this.allowedTeamSlugs.join(', ')}`, | 
					
						
							|  |  |  |       )); | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | } |