Skip to content

Commit f9bd624

Browse files
authored
Merge pull request #218 from per1234/access-control
Add Library Registry access control system
2 parents 77e2d18 + 16c5fb4 commit f9bd624

File tree

35 files changed

+387
-24
lines changed

35 files changed

+387
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
name: gopkg.in/yaml.v3
3+
version: v3.0.1
4+
type: go
5+
summary: Package yaml implements YAML support for the Go language.
6+
homepage: https://pkg.go.dev/gopkg.in/yaml.v3
7+
# Apache-2.0 subsumes MIT
8+
# https://www.gnu.org/licenses/license-compatibility.html#combining
9+
license: apache-2.0
10+
licenses:
11+
- sources: LICENSE
12+
text: |2
13+
14+
This project is covered by two different licenses: MIT and Apache.
15+
16+
#### MIT License ####
17+
18+
The following files were ported to Go from C files of libyaml, and thus
19+
are still covered by their original MIT license, with the additional
20+
copyright staring in 2011 when the project was ported over:
21+
22+
apic.go emitterc.go parserc.go readerc.go scannerc.go
23+
writerc.go yamlh.go yamlprivateh.go
24+
25+
Copyright (c) 2006-2010 Kirill Simonov
26+
Copyright (c) 2006-2011 Kirill Simonov
27+
28+
Permission is hereby granted, free of charge, to any person obtaining a copy of
29+
this software and associated documentation files (the "Software"), to deal in
30+
the Software without restriction, including without limitation the rights to
31+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
32+
of the Software, and to permit persons to whom the Software is furnished to do
33+
so, subject to the following conditions:
34+
35+
The above copyright notice and this permission notice shall be included in all
36+
copies or substantial portions of the Software.
37+
38+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44+
SOFTWARE.
45+
46+
### Apache License ###
47+
48+
All the remaining project files are covered by the Apache license:
49+
50+
Copyright (c) 2011-2019 Canonical Ltd
51+
52+
Licensed under the Apache License, Version 2.0 (the "License");
53+
you may not use this file except in compliance with the License.
54+
You may obtain a copy of the License at
55+
56+
http://www.apache.org/licenses/LICENSE-2.0
57+
58+
Unless required by applicable law or agreed to in writing, software
59+
distributed under the License is distributed on an "AS IS" BASIS,
60+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
61+
See the License for the specific language governing permissions and
62+
limitations under the License.
63+
- sources: README.md
64+
text: |-
65+
The yaml package is licensed under the MIT and Apache License 2.0 licenses.
66+
Please see the LICENSE file for details.
67+
notices:
68+
- sources: NOTICE
69+
text: |-
70+
Copyright 2011-2016 Canonical Ltd.
71+
72+
Licensed under the Apache License, Version 2.0 (the "License");
73+
you may not use this file except in compliance with the License.
74+
You may obtain a copy of the License at
75+
76+
http://www.apache.org/licenses/LICENSE-2.0
77+
78+
Unless required by applicable law or agreed to in writing, software
79+
distributed under the License is distributed on an "AS IS" BASIS,
80+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
81+
See the License for the specific language governing permissions and
82+
limitations under the License.

Diff for: go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ require (
77
github.com/arduino/go-properties-orderedmap v1.8.1
88
github.com/sourcegraph/go-diff v0.7.0
99
github.com/stretchr/testify v1.10.0
10+
gopkg.in/yaml.v3 v3.0.1
1011
)
1112

1213
require (
1314
github.com/davecgh/go-spew v1.1.1 // indirect
1415
github.com/pmezard/go-difflib v1.0.0 // indirect
15-
gopkg.in/yaml.v3 v3.0.1 // indirect
1616
)

Diff for: main.go

+107-17
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"strings"
3535

3636
"github.com/sourcegraph/go-diff/diff"
37+
"gopkg.in/yaml.v3"
3738

3839
"github.com/arduino/go-paths-helper"
3940
properties "github.com/arduino/go-properties-orderedmap"
@@ -67,6 +68,29 @@ var recommendedOrganizations []string = []string{
6768
"github.com/adafruit",
6869
}
6970

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+
7094
// request is the type of the request data.
7195
type request struct {
7296
Type string `json:"type"` // Request type.
@@ -89,14 +113,23 @@ type submissionType struct {
89113
}
90114

91115
// Command line flags.
116+
// Path of the access control file, relative to repopath.
117+
var accesslistArgument = flag.String("accesslist", "", "")
92118
var diffPathArgument = flag.String("diffpath", "", "")
93119
var repoPathArgument = flag.String("repopath", "", "")
94120
var listNameArgument = flag.String("listname", "", "")
95121

122+
// GitHub username of the user making the submission.
123+
var submitterArgument = flag.String("submitter", "", "")
124+
96125
func main() {
97126
// Validate flag input.
98127
flag.Parse()
99128

129+
if *accesslistArgument == "" {
130+
errorExit("--accesslist flag is required")
131+
}
132+
100133
if *diffPathArgument == "" {
101134
errorExit("--diffpath flag is required")
102135
}
@@ -109,8 +142,18 @@ func main() {
109142
errorExit("--listname flag is required")
110143
}
111144

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+
112155
diffPath := paths.New(*diffPathArgument)
113-
exist, err := diffPath.ExistCheck()
156+
exist, err = diffPath.ExistCheck()
114157
if !exist {
115158
errorExit("diff file not found")
116159
}
@@ -121,23 +164,59 @@ func main() {
121164
errorExit(fmt.Sprintf("list file %s not found", listPath))
122165
}
123166

124-
// Parse the PR diff.
125-
rawDiff, err := diffPath.ReadFile()
167+
rawAccessList, err := accesslistPath.ReadFile()
126168
if err != nil {
127169
panic(err)
128170
}
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+
129179
var req request
130180
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+
}
132203

133204
// Process the submissions.
134205
var indexEntries []string
135206
var indexerLogsURLs []string
207+
allowedSubmissions := false
136208
for _, submissionURL := range submissionURLs {
137-
submission, indexEntry := populateSubmission(submissionURL, listPath)
209+
submission, indexEntry, allowed := populateSubmission(submissionURL, listPath, accessList, submitterAccess)
138210
req.Submissions = append(req.Submissions, submission)
139211
indexEntries = append(indexEntries, indexEntry)
140212
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"
141220
}
142221

143222
// Check for duplicates within the submission itself.
@@ -240,7 +319,7 @@ func parseDiff(rawDiff []byte, listName string) (string, string, string, []strin
240319
}
241320

242321
// 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) {
244323
indexSourceSeparator := "|"
245324
var submission submissionType
246325

@@ -250,37 +329,48 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
250329
submissionURLObject, err := url.Parse(submission.SubmissionURL)
251330
if err != nil {
252331
submission.Error = fmt.Sprintf("Invalid submission URL (%s)", err)
253-
return submission, ""
332+
return submission, "", true
254333
}
255334

256335
// Check if URL is accessible.
257336
httpResponse, err := http.Get(submissionURLObject.String())
258337
if err != nil {
259338
submission.Error = fmt.Sprintf("Unable to load submission URL: %s", err)
260-
return submission, ""
339+
return submission, "", true
261340
}
262341
if httpResponse.StatusCode != http.StatusOK {
263342
submission.Error = "Unable to load submission URL. Is the repository public?"
264-
return submission, ""
343+
return submission, "", true
265344
}
266345

267346
// Resolve redirects and normalize.
268347
normalizedURLObject := normalizeURL(httpResponse.Request.URL)
269348

270349
submission.NormalizedURL = normalizedURLObject.String()
271350

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+
272362
// Check if URL is from a supported Git host.
273363
if !uRLIsUnder(normalizedURLObject, supportedHosts) {
274364
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
276366
}
277367

278368
// Check if URL is a Git repository
279369
err = exec.Command("git", "ls-remote", normalizedURLObject.String()).Run()
280370
if err != nil {
281371
if _, ok := err.(*exec.ExitError); ok {
282372
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
284374
}
285375

286376
panic(err)
@@ -299,7 +389,7 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
299389
normalizedListURLObject := normalizeURL(listURLObject)
300390
if normalizedListURLObject.String() == normalizedURLObject.String() {
301391
submission.Error = "Submission URL is already in the Library Manager index."
302-
return submission, ""
392+
return submission, "", true
303393
}
304394
}
305395

@@ -344,7 +434,7 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
344434
}
345435
if string(tagList) == "" {
346436
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
348438
}
349439
latestTag, err := exec.Command("git", "describe", "--tags", strings.TrimSpace(string(tagList))).Output()
350440
if err != nil {
@@ -362,18 +452,18 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
362452
libraryPropertiesPath := submissionClonePath.Join("library.properties")
363453
if !libraryPropertiesPath.Exist() {
364454
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
366456
}
367457
libraryProperties, err := properties.LoadFromPath(libraryPropertiesPath)
368458
if err != nil {
369459
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
371461
}
372462
var ok bool
373463
submission.Name, ok = libraryProperties.GetOk("name")
374464
if !ok {
375465
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
377467
}
378468

379469
// Assemble Library Manager index source entry string
@@ -386,7 +476,7 @@ func populateSubmission(submissionURL string, listPath *paths.Path) (submissionT
386476
indexSourceSeparator,
387477
)
388478

389-
return submission, indexEntry
479+
return submission, indexEntry, true
390480
}
391481

392482
// normalizeURL converts the URL into the standardized format used in the index.

0 commit comments

Comments
 (0)