@@ -3,6 +3,7 @@ import { XMLParser } from 'fast-xml-parser'
33import { ExtractorResult , ProvenanceEntry , RawContact } from '../../types'
44import { fetchText , isEmail , registryHeaders } from '../http'
55
6+ import { toHandleCandidates } from './handles'
67import { ParsedPurl } from './purl'
78
89const SOURCE = 'maven-pom'
@@ -74,6 +75,23 @@ function mapProjectDevelopers(project: any, sourceUrl: string, fetchedAt: string
7475 return contacts
7576}
7677
78+ // Developer <id>s are frequently GitHub logins but Central does not guarantee it (Apache ids
79+ // are LDAP usernames) — emitted as candidates for repo-contributor corroboration, and only for
80+ // developers that did not already yield an email contact.
81+ function mapProjectDeveloperHandles (
82+ project : any ,
83+ sourceUrl : string ,
84+ fetchedAt : string ,
85+ ) : RawContact [ ] {
86+ const handles : unknown [ ] = [ ]
87+ for ( const dev of asArray ( project . developers ?. developer ) ) {
88+ const raw = ( dev as any ) ?. email
89+ if ( typeof raw === 'string' && deobfuscateEmail ( raw ) ) continue
90+ handles . push ( ( dev as any ) ?. id )
91+ }
92+ return toHandleCandidates ( handles , SOURCE , sourceUrl , fetchedAt )
93+ }
94+
7795export function mapMavenPom ( xml : string , sourceUrl : string , fetchedAt : string ) : RawContact [ ] {
7896 const project = parseProject ( xml )
7997 if ( ! project ) return [ ]
@@ -162,6 +180,7 @@ function scmOwners(project: any): string[] {
162180interface ParentPom {
163181 group : string
164182 contacts : RawContact [ ]
183+ handles : RawContact [ ]
165184 owners : string [ ]
166185 parent : ParentRef | null
167186}
@@ -181,9 +200,11 @@ async function fetchParentPom(
181200 if ( ! text ) return null
182201 const project = parseProject ( text )
183202 if ( ! project ) return null
203+ const fetchedAt = new Date ( ) . toISOString ( )
184204 return {
185205 group : typeof project . groupId === 'string' ? project . groupId : ref . group ,
186- contacts : mapProjectDevelopers ( project , url , new Date ( ) . toISOString ( ) ) ,
206+ contacts : mapProjectDevelopers ( project , url , fetchedAt ) ,
207+ handles : mapProjectDeveloperHandles ( project , url , fetchedAt ) ,
187208 owners : scmOwners ( project ) ,
188209 parent : parentRef ( project ) ,
189210 }
@@ -212,8 +233,9 @@ async function traverseParents(
212233 repoUrl : string | undefined ,
213234 timeoutMs : number ,
214235 userAgent : string ,
215- ) : Promise < RawContact [ ] > {
236+ ) : Promise < { contacts : RawContact [ ] ; handleCandidates : RawContact [ ] } > {
216237 const repoOwner = repoUrl ? repoOwnerOf ( repoUrl ) : null
238+ const handleCandidates : RawContact [ ] = [ ]
217239 const seen = new Set < string > ( )
218240 let ref = parentRef ( leafProject )
219241 for ( let depth = 0 ; ref !== null && depth < MAX_PARENT_DEPTH ; depth ++ ) {
@@ -225,10 +247,11 @@ async function traverseParents(
225247 const related =
226248 groupRelated ( leafGroup , pom . group ) || ( repoOwner !== null && pom . owners . includes ( repoOwner ) )
227249 if ( ! related ) break
228- if ( pom . contacts . length > 0 ) return pom . contacts
250+ handleCandidates . push ( ...pom . handles )
251+ if ( pom . contacts . length > 0 ) return { contacts : pom . contacts , handleCandidates }
229252 ref = pom . parent
230253 }
231- return [ ]
254+ return { contacts : [ ] , handleCandidates }
232255}
233256
234257export async function fetchMaven (
@@ -250,9 +273,19 @@ export async function fetchMaven(
250273 const project = parseProject ( text )
251274 if ( ! project ) return { contacts : [ ] , policies : { } }
252275
253- let contacts = mapProjectDevelopers ( project , url , new Date ( ) . toISOString ( ) )
276+ const fetchedAt = new Date ( ) . toISOString ( )
277+ let contacts = mapProjectDevelopers ( project , url , fetchedAt )
278+ const handleCandidates = mapProjectDeveloperHandles ( project , url , fetchedAt )
254279 if ( contacts . length === 0 ) {
255- contacts = await traverseParents ( project , parsed . namespace , repoUrl , timeoutMs , userAgent )
280+ const inherited = await traverseParents (
281+ project ,
282+ parsed . namespace ,
283+ repoUrl ,
284+ timeoutMs ,
285+ userAgent ,
286+ )
287+ contacts = inherited . contacts
288+ handleCandidates . push ( ...inherited . handleCandidates )
256289 }
257- return { contacts, policies : { } }
290+ return { contacts, policies : { } , handleCandidates }
258291}
0 commit comments