@@ -34,6 +34,7 @@ import (
34
34
"strings"
35
35
36
36
"github.com/sourcegraph/go-diff/diff"
37
+ "gopkg.in/yaml.v3"
37
38
38
39
"github.com/arduino/go-paths-helper"
39
40
properties "github.com/arduino/go-properties-orderedmap"
@@ -67,6 +68,29 @@ var recommendedOrganizations []string = []string{
67
68
"github.com/adafruit" ,
68
69
}
69
70
71
+ // accessType is the type of the access control level.
72
+ type accessType string
73
+
74
+ const (
75
+ // Allow allows unrestricted access. The entity can make requests even for library repositories whose owner is denied
76
+ // access.
77
+ Allow accessType = "allow"
78
+ // Default gives default access. The entity can make requests as long as the owner of the library's repository is not
79
+ // denied access.
80
+ Default = "default"
81
+ // Deny denies all access. The entity is not permitted to make requests, and registration of libraries from
82
+ // repositories they own is not permitted.
83
+ Deny = "deny"
84
+ )
85
+
86
+ // accessDataType is the type of the access control data.
87
+ type accessDataType struct {
88
+ Access accessType `yaml:"access"` // Access level.
89
+ Host string `yaml:"host"` // Account host (e.g., `github.com`).
90
+ Name string `yaml:"name"` // User or organization account name.
91
+ Reference string `yaml:"reference"` // URL that provides additional information about the access control entry.
92
+ }
93
+
70
94
// request is the type of the request data.
71
95
type request struct {
72
96
Type string `json:"type"` // Request type.
@@ -89,14 +113,23 @@ type submissionType struct {
89
113
}
90
114
91
115
// Command line flags.
116
+ // Path of the access control file, relative to repopath.
117
+ var accesslistArgument = flag .String ("accesslist" , "" , "" )
92
118
var diffPathArgument = flag .String ("diffpath" , "" , "" )
93
119
var repoPathArgument = flag .String ("repopath" , "" , "" )
94
120
var listNameArgument = flag .String ("listname" , "" , "" )
95
121
122
+ // GitHub username of the user making the submission.
123
+ var submitterArgument = flag .String ("submitter" , "" , "" )
124
+
96
125
func main () {
97
126
// Validate flag input.
98
127
flag .Parse ()
99
128
129
+ if * accesslistArgument == "" {
130
+ errorExit ("--accesslist flag is required" )
131
+ }
132
+
100
133
if * diffPathArgument == "" {
101
134
errorExit ("--diffpath flag is required" )
102
135
}
@@ -109,8 +142,18 @@ func main() {
109
142
errorExit ("--listname flag is required" )
110
143
}
111
144
145
+ if * submitterArgument == "" {
146
+ errorExit ("--submitter flag is required" )
147
+ }
148
+
149
+ accesslistPath := paths .New (* repoPathArgument , * accesslistArgument )
150
+ exist , err := accesslistPath .ExistCheck ()
151
+ if ! exist {
152
+ errorExit ("Access control file not found" )
153
+ }
154
+
112
155
diffPath := paths .New (* diffPathArgument )
113
- exist , err : = diffPath .ExistCheck ()
156
+ exist , err = diffPath .ExistCheck ()
114
157
if ! exist {
115
158
errorExit ("diff file not found" )
116
159
}
@@ -121,23 +164,59 @@ func main() {
121
164
errorExit (fmt .Sprintf ("list file %s not found" , listPath ))
122
165
}
123
166
124
- // Parse the PR diff.
125
- rawDiff , err := diffPath .ReadFile ()
167
+ rawAccessList , err := accesslistPath .ReadFile ()
126
168
if err != nil {
127
169
panic (err )
128
170
}
171
+
172
+ // Unmarshal access control file.
173
+ var accessList []accessDataType
174
+ err = yaml .Unmarshal (rawAccessList , & accessList )
175
+ if err != nil {
176
+ errorExit (fmt .Sprintf ("Access control file has invalid format:\n \n %s" , err ))
177
+ }
178
+
129
179
var req request
130
180
var submissionURLs []string
131
- req .Type , req .Error , req .ArduinoLintLibraryManagerSetting , submissionURLs = parseDiff (rawDiff , * listNameArgument )
181
+
182
+ // Determine access level of submitter.
183
+ var submitterAccess accessType = Default
184
+ for _ , accessData := range accessList {
185
+ if accessData .Host == "github.com" && * submitterArgument == accessData .Name {
186
+ submitterAccess = accessData .Access
187
+ if submitterAccess == Deny {
188
+ req .Type = "declined"
189
+ req .Error = fmt .Sprintf ("Library registry privileges for @%s have been revoked.%%0ASee: %s" , * submitterArgument , accessData .Reference )
190
+ }
191
+ break
192
+ }
193
+ }
194
+
195
+ if req .Error == "" {
196
+ // Parse the PR diff.
197
+ rawDiff , err := diffPath .ReadFile ()
198
+ if err != nil {
199
+ panic (err )
200
+ }
201
+ req .Type , req .Error , req .ArduinoLintLibraryManagerSetting , submissionURLs = parseDiff (rawDiff , * listNameArgument )
202
+ }
132
203
133
204
// Process the submissions.
134
205
var indexEntries []string
135
206
var indexerLogsURLs []string
207
+ allowedSubmissions := false
136
208
for _ , submissionURL := range submissionURLs {
137
- submission , indexEntry := populateSubmission (submissionURL , listPath )
209
+ submission , indexEntry , allowed := populateSubmission (submissionURL , listPath , accessList , submitterAccess )
138
210
req .Submissions = append (req .Submissions , submission )
139
211
indexEntries = append (indexEntries , indexEntry )
140
212
indexerLogsURLs = append (indexerLogsURLs , indexerLogsURL (submission .NormalizedURL ))
213
+ if allowed {
214
+ allowedSubmissions = true
215
+ }
216
+ }
217
+ if len (submissionURLs ) > 0 && ! allowedSubmissions {
218
+ // If none of the submissions are allowed, decline the request.
219
+ req .Type = "declined"
141
220
}
142
221
143
222
// Check for duplicates within the submission itself.
@@ -240,7 +319,7 @@ func parseDiff(rawDiff []byte, listName string) (string, string, string, []strin
240
319
}
241
320
242
321
// populateSubmission does the checks on the submission that aren't provided by Arduino Lint and gathers the necessary data on it.
243
- func populateSubmission (submissionURL string , listPath * paths.Path ) (submissionType , string ) {
322
+ func populateSubmission (submissionURL string , listPath * paths.Path , accessList [] accessDataType , submitterAccess accessType ) (submissionType , string , bool ) {
244
323
indexSourceSeparator := "|"
245
324
var submission submissionType
246
325
@@ -250,37 +329,48 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
250
329
submissionURLObject , err := url .Parse (submission .SubmissionURL )
251
330
if err != nil {
252
331
submission .Error = fmt .Sprintf ("Invalid submission URL (%s)" , err )
253
- return submission , ""
332
+ return submission , "" , true
254
333
}
255
334
256
335
// Check if URL is accessible.
257
336
httpResponse , err := http .Get (submissionURLObject .String ())
258
337
if err != nil {
259
338
submission .Error = fmt .Sprintf ("Unable to load submission URL: %s" , err )
260
- return submission , ""
339
+ return submission , "" , true
261
340
}
262
341
if httpResponse .StatusCode != http .StatusOK {
263
342
submission .Error = "Unable to load submission URL. Is the repository public?"
264
- return submission , ""
343
+ return submission , "" , true
265
344
}
266
345
267
346
// Resolve redirects and normalize.
268
347
normalizedURLObject := normalizeURL (httpResponse .Request .URL )
269
348
270
349
submission .NormalizedURL = normalizedURLObject .String ()
271
350
351
+ if submitterAccess != Allow {
352
+ // Check library repository owner access.
353
+ for _ , accessData := range accessList {
354
+ ownerSlug := fmt .Sprintf ("%s/%s" , accessData .Host , accessData .Name )
355
+ if accessData .Access == Deny && uRLIsUnder (normalizedURLObject , []string {ownerSlug }) {
356
+ submission .Error = fmt .Sprintf ("Library registry privileges for library repository owner `%s` have been revoked.%%0ASee: %s" , ownerSlug , accessData .Reference )
357
+ return submission , "" , false
358
+ }
359
+ }
360
+ }
361
+
272
362
// Check if URL is from a supported Git host.
273
363
if ! uRLIsUnder (normalizedURLObject , supportedHosts ) {
274
364
submission .Error = fmt .Sprintf ("`%s` is not currently supported as a Git hosting website for Library Manager.%%0A%%0ASee: https://github.com/arduino/library-registry/blob/main/FAQ.md#what-are-the-requirements-for-a-library-to-be-added-to-library-manager" , normalizedURLObject .Host )
275
- return submission , ""
365
+ return submission , "" , true
276
366
}
277
367
278
368
// Check if URL is a Git repository
279
369
err = exec .Command ("git" , "ls-remote" , normalizedURLObject .String ()).Run ()
280
370
if err != nil {
281
371
if _ , ok := err .(* exec.ExitError ); ok {
282
372
submission .Error = "Submission URL is not a Git clone URL (e.g., `https://github.com/arduino-libraries/Servo`)."
283
- return submission , ""
373
+ return submission , "" , true
284
374
}
285
375
286
376
panic (err )
@@ -299,7 +389,7 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
299
389
normalizedListURLObject := normalizeURL (listURLObject )
300
390
if normalizedListURLObject .String () == normalizedURLObject .String () {
301
391
submission .Error = "Submission URL is already in the Library Manager index."
302
- return submission , ""
392
+ return submission , "" , true
303
393
}
304
394
}
305
395
@@ -344,7 +434,7 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
344
434
}
345
435
if string (tagList ) == "" {
346
436
submission .Error = "The repository has no tags. You need to create a [release](https://docs.github.com/en/github/administering-a-repository/managing-releases-in-a-repository) or [tag](https://git-scm.com/docs/git-tag) that matches the `version` value in the library's library.properties file."
347
- return submission , ""
437
+ return submission , "" , true
348
438
}
349
439
latestTag , err := exec .Command ("git" , "describe" , "--tags" , strings .TrimSpace (string (tagList ))).Output ()
350
440
if err != nil {
@@ -362,18 +452,18 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
362
452
libraryPropertiesPath := submissionClonePath .Join ("library.properties" )
363
453
if ! libraryPropertiesPath .Exist () {
364
454
submission .Error = "Library is missing a library.properties metadata file.%0A%0ASee: https://arduino.github.io/arduino-cli/latest/library-specification/#library-metadata"
365
- return submission , ""
455
+ return submission , "" , true
366
456
}
367
457
libraryProperties , err := properties .LoadFromPath (libraryPropertiesPath )
368
458
if err != nil {
369
459
submission .Error = fmt .Sprintf ("Invalid library.properties file: %s%%0A%%0ASee: https://arduino.github.io/arduino-cli/latest/library-specification/#library-metadata" , err )
370
- return submission , ""
460
+ return submission , "" , true
371
461
}
372
462
var ok bool
373
463
submission .Name , ok = libraryProperties .GetOk ("name" )
374
464
if ! ok {
375
465
submission .Error = "library.properties is missing a name field.%0A%0ASee: https://arduino.github.io/arduino-cli/latest/library-specification/#library-metadata"
376
- return submission , ""
466
+ return submission , "" , true
377
467
}
378
468
379
469
// Assemble Library Manager index source entry string
@@ -386,7 +476,7 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
386
476
indexSourceSeparator ,
387
477
)
388
478
389
- return submission , indexEntry
479
+ return submission , indexEntry , true
390
480
}
391
481
392
482
// normalizeURL converts the URL into the standardized format used in the index.
0 commit comments