@@ -79,27 +79,27 @@ export async function accessTokenManager(options: {
79
79
owner : tokenRequest . owner ,
80
80
} ) ;
81
81
if ( ! appInstallation ) {
82
- throw new GithubAccessTokenError ( createErrorMessage ( [ {
82
+ throw new GithubAccessTokenError ( [ {
83
83
owner : tokenRequest . owner ,
84
84
// BE AWARE to prevent leaking owner existence
85
85
issues : tokenRequest . owner !== callerIdentity . repository_owner ?
86
86
[ NOT_AUTHORIZED_MESSAGE ] :
87
87
[ `'${ GITHUB_APP . name } ' has not been installed. Install from ${ GITHUB_APP . html_url } ` ] ,
88
- } ] , effectiveCallerIdentitySubjects ) ) ;
88
+ } ] , effectiveCallerIdentitySubjects ) ;
89
89
}
90
90
log . debug ( { appInstallation} , 'App installation' ) ;
91
91
92
92
const accessPolicyPaths = [ ...options . accessPolicyLocation . owner . paths , ...options . accessPolicyLocation . repo . paths ] ;
93
93
if ( ! accessPolicyPaths . every ( ( path ) => appInstallation . single_file_paths ?. includes ( path ) ) ) {
94
94
log . debug ( { required : accessPolicyPaths , actual : appInstallation . single_file_paths } ,
95
95
`App installation is missing 'single_file' permission for access policy file(s)` ) ;
96
- throw new GithubAccessTokenError ( createErrorMessage ( [ {
96
+ throw new GithubAccessTokenError ( [ {
97
97
owner : tokenRequest . owner ,
98
98
// BE AWARE to prevent leaking owner existence
99
99
issues : callerIdentity . repository !== `${ tokenRequest . owner } /${ options . accessPolicyLocation . owner . repo } ` ?
100
100
[ NOT_AUTHORIZED_MESSAGE ] :
101
101
[ `'${ GITHUB_APP . name } ' installation is missing 'single_file' permission for access policy file(s)` ] ,
102
- } ] , effectiveCallerIdentitySubjects ) ) ;
102
+ } ] , effectiveCallerIdentitySubjects ) ;
103
103
}
104
104
105
105
// --- verify requested token permissions ------------------------------------------------------------------------
@@ -112,7 +112,7 @@ export async function accessTokenManager(options: {
112
112
113
113
if ( requestedAppInstallationPermissions . denied . length > 0 ) {
114
114
// TODO potential security issue: Do not leak app installation permissions
115
- throw new GithubAccessTokenError ( createErrorMessage ( [ {
115
+ throw new GithubAccessTokenError ( [ {
116
116
owner : tokenRequest . owner ,
117
117
// BE AWARE to prevent leaking owner existence
118
118
issues : callerIdentity . repository !== `${ tokenRequest . owner } /${ options . accessPolicyLocation . owner . repo } ` ?
@@ -121,7 +121,7 @@ export async function accessTokenManager(options: {
121
121
scope, permission,
122
122
message : `Permission has not been granted to '${ GITHUB_APP . name } ' installation` ,
123
123
} ) ) ,
124
- } ] , effectiveCallerIdentitySubjects ) ) ;
124
+ } ] , effectiveCallerIdentitySubjects ) ;
125
125
}
126
126
127
127
const appInstallationClient = await createOctokit ( GITHUB_APP_CLIENT , appInstallation , {
@@ -138,14 +138,14 @@ export async function accessTokenManager(options: {
138
138
} ) . catch ( ( error ) => {
139
139
if ( error instanceof GithubAccessPolicyError ) {
140
140
log . debug ( { issues : error . issues } , `'${ tokenRequest . owner } ' access policy` ) ;
141
- throw new GithubAccessTokenError ( createErrorMessage ( [ {
141
+ throw new GithubAccessTokenError ( [ {
142
142
owner : tokenRequest . owner ,
143
143
// BE AWARE to prevent leaking owner existence
144
144
issues : tokenRequest . owner !== callerIdentity . repository_owner ?
145
145
[ NOT_AUTHORIZED_MESSAGE ] :
146
146
[ formatAccessPolicyError ( error ) ] ,
147
147
148
- } ] , effectiveCallerIdentitySubjects ) ) ;
148
+ } ] , effectiveCallerIdentitySubjects ) ;
149
149
}
150
150
throw error ;
151
151
} ) ;
@@ -158,13 +158,13 @@ export async function accessTokenManager(options: {
158
158
[ `repo:${ tokenRequest . owner } /*:**` ] ; // e.g., ['repo:qoomon/*:**' ]
159
159
160
160
if ( ! matchSubject ( allowedSubjects , effectiveCallerIdentitySubjects ) ) {
161
- throw new GithubAccessTokenError ( createErrorMessage ( [ {
161
+ throw new GithubAccessTokenError ( [ {
162
162
owner : tokenRequest . owner ,
163
163
// BE AWARE to prevent leaking owner existence
164
164
issues : tokenRequest . owner !== callerIdentity . repository_owner ?
165
165
[ NOT_AUTHORIZED_MESSAGE ] :
166
166
[ 'OIDC token subject is not allowed by owner access policy' ] ,
167
- } ] , effectiveCallerIdentitySubjects ) ) ;
167
+ } ] , effectiveCallerIdentitySubjects ) ;
168
168
}
169
169
170
170
// grant requested permissions explicitly to prevent accidental permission escalation
@@ -182,11 +182,11 @@ export async function accessTokenManager(options: {
182
182
// --- verify requested permissions against owner access policy ----------------------------------------------
183
183
184
184
if ( ! hasEntries ( ownerGrantedPermissions ) ) {
185
- throw new GithubAccessTokenError ( createErrorMessage ( [ {
185
+ throw new GithubAccessTokenError ( [ {
186
186
owner : tokenRequest . owner ,
187
187
// BE AWARE to prevent leaking owner existence
188
188
issues : [ NOT_AUTHORIZED_MESSAGE ] ,
189
- } ] , effectiveCallerIdentitySubjects ) ) ;
189
+ } ] , effectiveCallerIdentitySubjects ) ;
190
190
}
191
191
192
192
const requestedOwnerPermissions = verifyPermissions ( {
@@ -196,13 +196,13 @@ export async function accessTokenManager(options: {
196
196
197
197
// -- deny permissions
198
198
if ( requestedOwnerPermissions . denied . length > 0 ) {
199
- throw new GithubAccessTokenError ( createErrorMessage ( [ {
199
+ throw new GithubAccessTokenError ( [ {
200
200
owner : tokenRequest . owner ,
201
201
issues : requestedOwnerPermissions . denied . map ( ( { scope, permission} ) => ( {
202
202
scope, permission,
203
203
message : NOT_AUTHORIZED_MESSAGE ,
204
204
} ) ) ,
205
- } ] , effectiveCallerIdentitySubjects ) ) ;
205
+ } ] , effectiveCallerIdentitySubjects ) ;
206
206
}
207
207
208
208
// --- grant permissions
@@ -244,13 +244,13 @@ export async function accessTokenManager(options: {
244
244
// -- deny permissions
245
245
if ( requestedRepositoryPermissions . denied . length > 0 ) {
246
246
// TODO Potential security issue: Do not leak repository existence
247
- throw new GithubAccessTokenError ( createErrorMessage ( [ {
247
+ throw new GithubAccessTokenError ( [ {
248
248
owner : tokenRequest . owner ,
249
249
issues : requestedRepositoryPermissions . denied . map ( ( { scope, permission} ) => ( {
250
250
scope, permission,
251
251
message : NOT_AUTHORIZED_MESSAGE ,
252
252
} ) ) ,
253
- } ] , effectiveCallerIdentitySubjects ) ) ;
253
+ } ] , effectiveCallerIdentitySubjects ) ;
254
254
}
255
255
}
256
256
}
@@ -349,7 +349,7 @@ export async function accessTokenManager(options: {
349
349
350
350
if ( hasEntries ( requestedTokenIssues ) ) {
351
351
// TODO Potential security issue: Do not leak repository existence
352
- throw new GithubAccessTokenError ( createErrorMessage ( requestedTokenIssues , effectiveCallerIdentitySubjects ) ) ;
352
+ throw new GithubAccessTokenError ( requestedTokenIssues , effectiveCallerIdentitySubjects ) ;
353
353
}
354
354
}
355
355
@@ -387,41 +387,13 @@ export async function accessTokenManager(options: {
387
387
// --- Access Manager Functions --------------------------------------------------------------------------------------
388
388
389
389
/**
390
- * Create error message
391
- * @param reasons - error reasons
392
- * @param callerIdentitySubjects - caller identity subjects
393
- * @return error message
390
+ * Format access policy error
391
+ * @param error - access policy error
392
+ * @return formatted error message
394
393
*/
395
- function createErrorMessage (
396
- reasons : ( {
397
- owner : string ,
398
- issues : ( string | { scope : string , permission : string , message : string } ) [ ] ,
399
- } | {
400
- owner : string , repo : string ,
401
- issues : ( string | { scope : string , permission : string , message : string } ) [ ] ,
402
- } ) [ ] ,
403
- callerIdentitySubjects : string [ ] ,
404
- ) : string {
405
- let message = 'Issues:\n' +
406
- reasons . map ( ( reason ) => {
407
- let messagePrefix = reason . owner ;
408
- if ( 'repo' in reason && reason . repo ) {
409
- messagePrefix += `/${ reason . repo } ` ;
410
- }
411
- return `${ messagePrefix } :\n` +
412
- reason . issues . map ( ( issue ) => {
413
- if ( typeof issue === 'string' ) {
414
- return issue ;
415
- }
416
- return `${ issue . scope } : ${ issue . permission } - ${ issue . message } ` ;
417
- } ) . map ( ( message ) => indent ( message , '- ' ) ) . join ( '\n' ) ;
418
- } ) . map ( ( message ) => indent ( message , '- ' ) ) . join ( '\n' ) ;
419
-
420
- message += '\n' +
421
- 'Effective OIDC token subjects:\n' +
422
- `${ callerIdentitySubjects . map ( ( subject ) => indent ( subject , '- ' ) ) . join ( '\n' ) } ` ;
423
-
424
- return message ;
394
+ function formatAccessPolicyError ( error : GithubAccessPolicyError ) {
395
+ return error . message + ( ! error . issues ?. length ? '' : '\n' +
396
+ error . issues . map ( ( issue ) => indent ( issue , '- ' ) ) . join ( '\n' ) ) ;
425
397
}
426
398
427
399
/**
@@ -816,16 +788,6 @@ function regexpOfSubjectPattern(subjectPattern: string): RegExp {
816
788
return RegExp ( `^${ regexp } $` , 'i' ) ;
817
789
}
818
790
819
- /**
820
- * Format access policy error
821
- * @param error - access policy error
822
- * @return formatted error message
823
- */
824
- function formatAccessPolicyError ( error : GithubAccessPolicyError ) {
825
- return error . message + ( ! error . issues ?. length ? '' : '\n' +
826
- error . issues . map ( ( issue ) => indent ( issue , '- ' ) ) . join ( '\n' ) ) ;
827
- }
828
-
829
791
// --- GitHub Functions ----------------------------------------------------------------------------------------------
830
792
831
793
/**
@@ -941,10 +903,58 @@ async function getRepositoryFileContent(client: Octokit, {
941
903
export class GithubAccessTokenError extends Error {
942
904
/**
943
905
* Creates a new GitHub access token error
944
- * @param msg - error message
906
+ * @param reasons - error reasons
907
+ * @param callerIdentitySubjects - caller identity subjects
945
908
*/
946
- constructor ( msg : string ) {
947
- super ( msg ) ;
909
+ constructor (
910
+ reasons : ( {
911
+ owner : string ,
912
+ issues : ( string | { scope : string , permission : string , message : string } ) [ ] ,
913
+ } | {
914
+ owner : string , repo : string ,
915
+ issues : ( string | { scope : string , permission : string , message : string } ) [ ] ,
916
+ } ) [ ] ,
917
+ callerIdentitySubjects : string [ ] ,
918
+ ) {
919
+ super ( createAccessTokenRequestErrorMessage ( reasons , callerIdentitySubjects ) ) ;
920
+
921
+ /**
922
+ * Create error message
923
+ * @param reasons - error reasons
924
+ * @param callerIdentitySubjects - caller identity subjects
925
+ * @return error message
926
+ */
927
+ function createAccessTokenRequestErrorMessage (
928
+ reasons : ( {
929
+ owner : string ,
930
+ issues : ( string | { scope : string , permission : string , message : string } ) [ ] ,
931
+ } | {
932
+ owner : string , repo : string ,
933
+ issues : ( string | { scope : string , permission : string , message : string } ) [ ] ,
934
+ } ) [ ] ,
935
+ callerIdentitySubjects : string [ ] ,
936
+ ) : string {
937
+ let message = 'Issues:\n' +
938
+ reasons . map ( ( reason ) => {
939
+ let messagePrefix = reason . owner ;
940
+ if ( 'repo' in reason && reason . repo ) {
941
+ messagePrefix += `/${ reason . repo } ` ;
942
+ }
943
+ return `${ messagePrefix } :\n` +
944
+ reason . issues . map ( ( issue ) => {
945
+ if ( typeof issue === 'string' ) {
946
+ return issue ;
947
+ }
948
+ return `${ issue . scope } : ${ issue . permission } - ${ issue . message } ` ;
949
+ } ) . map ( ( message ) => indent ( message , '- ' ) ) . join ( '\n' ) ;
950
+ } ) . map ( ( message ) => indent ( message , '- ' ) ) . join ( '\n' ) ;
951
+
952
+ message += '\n' +
953
+ 'Effective OIDC token subjects:\n' +
954
+ `${ callerIdentitySubjects . map ( ( subject ) => indent ( subject , '- ' ) ) . join ( '\n' ) } ` ;
955
+
956
+ return message ;
957
+ }
948
958
949
959
Object . setPrototypeOf ( this , GithubAccessTokenError . prototype ) ;
950
960
}
0 commit comments