| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  | // Imports
 | 
					
						
							|  |  |  | import * as jwt from 'jsonwebtoken'; | 
					
						
							| 
									
										
										
										
											2017-06-19 01:15:07 +03:00
										 |  |  | import {GithubPullRequests, PullRequest} 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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-19 01:15:07 +03:00
										 |  |  | // Enums
 | 
					
						
							|  |  |  | export enum BUILD_VERIFICATION_STATUS { | 
					
						
							|  |  |  |   verifiedAndTrusted, | 
					
						
							|  |  |  |   verifiedNotTrusted, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  | // Classes
 | 
					
						
							|  |  |  | export class BuildVerifier { | 
					
						
							|  |  |  |   // Properties - Protected
 | 
					
						
							|  |  |  |   protected githubPullRequests: GithubPullRequests; | 
					
						
							|  |  |  |   protected githubTeams: GithubTeams; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Constructor
 | 
					
						
							|  |  |  |   constructor(protected secret: string, githubToken: string, protected repoSlug: string, organization: string, | 
					
						
							| 
									
										
										
										
											2017-06-19 01:15:07 +03:00
										 |  |  |               protected allowedTeamSlugs: string[], protected trustedPrLabel: string) { | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  |     assertNotMissingOrEmpty('secret', secret); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('githubToken', githubToken); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('repoSlug', repoSlug); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('organization', organization); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('allowedTeamSlugs', allowedTeamSlugs && allowedTeamSlugs.join('')); | 
					
						
							| 
									
										
										
										
											2017-06-19 01:15:07 +03:00
										 |  |  |     assertNotMissingOrEmpty('trustedPrLabel', trustedPrLabel); | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.githubPullRequests = new GithubPullRequests(githubToken, repoSlug); | 
					
						
							|  |  |  |     this.githubTeams = new GithubTeams(githubToken, organization); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Methods - Public
 | 
					
						
							| 
									
										
										
										
											2017-06-19 01:15:07 +03:00
										 |  |  |   public getPrIsTrusted(pr: number): Promise<boolean> { | 
					
						
							| 
									
										
										
										
											2017-03-07 11:39:37 +02:00
										 |  |  |     return Promise.resolve(). | 
					
						
							|  |  |  |       then(() => this.githubPullRequests.fetch(pr)). | 
					
						
							| 
									
										
										
										
											2017-06-19 01:15:07 +03:00
										 |  |  |       then(prInfo => this.hasLabel(prInfo, this.trustedPrLabel) || | 
					
						
							|  |  |  |                      this.githubTeams.isMemberBySlug(prInfo.user.login, this.allowedTeamSlugs)); | 
					
						
							| 
									
										
										
										
											2017-03-07 11:39:37 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-19 01:15:07 +03:00
										 |  |  |   public verify(expectedPr: number, authHeader: string): Promise<BUILD_VERIFICATION_STATUS> { | 
					
						
							| 
									
										
										
										
											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-06-19 01:15:07 +03:00
										 |  |  |   protected hasLabel(prInfo: PullRequest, label: string) { | 
					
						
							|  |  |  |     return prInfo.labels.some(labelObj => labelObj.name === label); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-06-19 01:15:07 +03:00
										 |  |  |   protected verifyPr(pr: number): Promise<BUILD_VERIFICATION_STATUS> { | 
					
						
							|  |  |  |     return this.getPrIsTrusted(pr). | 
					
						
							|  |  |  |       then(isTrusted => Promise.resolve(isTrusted ? | 
					
						
							|  |  |  |           BUILD_VERIFICATION_STATUS.verifiedAndTrusted : | 
					
						
							|  |  |  |           BUILD_VERIFICATION_STATUS.verifiedNotTrusted)); | 
					
						
							| 
									
										
										
										
											2017-02-28 21:02:56 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | } |