-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
466 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package github | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/google/go-github/v65/github" | ||
) | ||
|
||
// see: https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps?apiVersion=2022-11-28 | ||
const ( | ||
// GitHub App install permission 'none'. | ||
AppInstallPermissionNone = "none" | ||
// GitHub App install permission 'read'. | ||
AppInstallPermissionRead = "read" | ||
// GitHub App install permission 'write'. | ||
AppInstallPermissionWrite = "write" | ||
) | ||
|
||
const ( | ||
// GitHub App install contents resource. | ||
AppInstallResourceContents = "contents" | ||
// GitHub App install checks resource. | ||
AppInstallResourceChecks = "checks" | ||
// GitHub App install packages resource. | ||
AppInstallResourcePackages = "packages" | ||
// add more supported resources as needed. | ||
) | ||
|
||
// GetInstallationPermission takes permissions and returns the permission level if valid. | ||
func GetInstallationPermission(resource string, appPermissions *github.InstallationPermissions) (string, error) { | ||
switch resource { | ||
case AppInstallResourceContents: | ||
return appPermissions.GetContents(), nil | ||
case AppInstallResourceChecks: | ||
return appPermissions.GetChecks(), nil | ||
case AppInstallResourcePackages: | ||
return appPermissions.GetPackages(), nil | ||
// add more supported resources as needed. | ||
default: | ||
return "", fmt.Errorf("given permission resource not supported: %s", resource) | ||
} | ||
} | ||
|
||
// ApplyInstallationPermissions takes permissions and applies a new permission if valid. | ||
func ApplyInstallationPermissions(resource, perm string, perms *github.InstallationPermissions) (*github.InstallationPermissions, error) { | ||
// convert permissions from string | ||
switch strings.ToLower(perm) { | ||
case AppInstallPermissionNone: | ||
case AppInstallPermissionRead: | ||
case AppInstallPermissionWrite: | ||
break | ||
default: | ||
return perms, fmt.Errorf("invalid permission level given for <resource>:<level> in %s:%s", resource, perm) | ||
} | ||
|
||
// convert resource from string | ||
switch strings.ToLower(resource) { | ||
case AppInstallResourceContents: | ||
perms.Contents = github.String(perm) | ||
case AppInstallResourceChecks: | ||
perms.Checks = github.String(perm) | ||
case AppInstallResourcePackages: | ||
perms.Packages = github.String(perm) | ||
// add more supported resources as needed. | ||
default: | ||
return perms, fmt.Errorf("invalid permission resource given for <resource>:<level> in %s:%s", resource, perm) | ||
} | ||
|
||
return perms, nil | ||
} | ||
|
||
// InstallationHasPermission takes a resource:perm pair and checks if the actual permission matches the expected permission or is supersceded by a higher permission. | ||
func InstallationHasPermission(resource, requiredPerm, actualPerm string) error { | ||
if len(actualPerm) == 0 { | ||
return fmt.Errorf("github app missing permission %s:%s", resource, requiredPerm) | ||
} | ||
|
||
permitted := false | ||
|
||
switch requiredPerm { | ||
case AppInstallPermissionNone: | ||
permitted = true | ||
case AppInstallPermissionRead: | ||
if actualPerm == AppInstallPermissionRead || | ||
actualPerm == AppInstallPermissionWrite { | ||
permitted = true | ||
} | ||
case AppInstallPermissionWrite: | ||
if actualPerm == AppInstallPermissionWrite { | ||
permitted = true | ||
} | ||
default: | ||
return fmt.Errorf("invalid required permission type: %s", requiredPerm) | ||
} | ||
|
||
if !permitted { | ||
return fmt.Errorf("github app requires permission %s:%s, found: %s", AppInstallResourceContents, AppInstallPermissionRead, actualPerm) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package github | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-github/v65/github" | ||
) | ||
|
||
func TestGetInstallationPermission(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
resource string | ||
permissions *github.InstallationPermissions | ||
expectedPerm string | ||
expectedError bool | ||
}{ | ||
{ | ||
name: "valid contents permission", | ||
resource: AppInstallResourceContents, | ||
permissions: &github.InstallationPermissions{Contents: github.String(AppInstallPermissionRead)}, | ||
expectedPerm: AppInstallPermissionRead, | ||
}, | ||
{ | ||
name: "valid checks permission", | ||
resource: AppInstallResourceChecks, | ||
permissions: &github.InstallationPermissions{Checks: github.String(AppInstallPermissionWrite)}, | ||
expectedPerm: AppInstallPermissionWrite, | ||
}, | ||
{ | ||
name: "valid packages permission", | ||
resource: AppInstallResourcePackages, | ||
permissions: &github.InstallationPermissions{Packages: github.String(AppInstallPermissionNone)}, | ||
expectedPerm: AppInstallPermissionNone, | ||
}, | ||
{ | ||
name: "invalid resource", | ||
resource: "invalid_resource", | ||
permissions: &github.InstallationPermissions{}, | ||
expectedError: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
perm, err := GetInstallationPermission(tt.resource, tt.permissions) | ||
if (err != nil) != tt.expectedError { | ||
t.Errorf("GetInstallationPermission() error = %v, expectedError %v", err, tt.expectedError) | ||
return | ||
} | ||
if perm != tt.expectedPerm { | ||
t.Errorf("GetInstallationPermission() = %v, expected %v", perm, tt.expectedPerm) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestApplyInstallationPermissions(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
resource string | ||
perm string | ||
initialPerms *github.InstallationPermissions | ||
expectedPerms *github.InstallationPermissions | ||
expectedError bool | ||
}{ | ||
{ | ||
name: "apply read permission to contents", | ||
resource: AppInstallResourceContents, | ||
perm: AppInstallPermissionRead, | ||
initialPerms: &github.InstallationPermissions{}, | ||
expectedPerms: &github.InstallationPermissions{ | ||
Contents: github.String(AppInstallPermissionRead), | ||
}, | ||
}, | ||
{ | ||
name: "apply write permission to checks", | ||
resource: AppInstallResourceChecks, | ||
perm: AppInstallPermissionWrite, | ||
initialPerms: &github.InstallationPermissions{}, | ||
expectedPerms: &github.InstallationPermissions{ | ||
Checks: github.String(AppInstallPermissionWrite), | ||
}, | ||
}, | ||
{ | ||
name: "apply none permission to packages", | ||
resource: AppInstallResourcePackages, | ||
perm: AppInstallPermissionNone, | ||
initialPerms: &github.InstallationPermissions{}, | ||
expectedPerms: &github.InstallationPermissions{ | ||
Packages: github.String(AppInstallPermissionNone), | ||
}, | ||
}, | ||
{ | ||
name: "invalid permission level", | ||
resource: AppInstallResourceContents, | ||
perm: "invalid_perm", | ||
initialPerms: &github.InstallationPermissions{}, | ||
expectedError: true, | ||
}, | ||
{ | ||
name: "invalid resource", | ||
resource: "invalid_resource", | ||
perm: AppInstallPermissionRead, | ||
initialPerms: &github.InstallationPermissions{}, | ||
expectedError: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
perms, err := ApplyInstallationPermissions(tt.resource, tt.perm, tt.initialPerms) | ||
if (err != nil) != tt.expectedError { | ||
t.Errorf("ApplyInstallationPermissions() error = %v, expectedError %v", err, tt.expectedError) | ||
return | ||
} | ||
if !tt.expectedError && !comparePermissions(perms, tt.expectedPerms) { | ||
t.Errorf("ApplyInstallationPermissions() = %v, expected %v", perms, tt.expectedPerms) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestInstallationHasPermission(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
resource string | ||
requiredPerm string | ||
actualPerm string | ||
expectedError bool | ||
}{ | ||
{ | ||
name: "valid read permission", | ||
resource: AppInstallResourceContents, | ||
requiredPerm: AppInstallPermissionRead, | ||
actualPerm: AppInstallPermissionRead, | ||
}, | ||
{ | ||
name: "valid write permission", | ||
resource: AppInstallResourceChecks, | ||
requiredPerm: AppInstallPermissionWrite, | ||
actualPerm: AppInstallPermissionWrite, | ||
}, | ||
{ | ||
name: "valid none permission", | ||
resource: AppInstallResourcePackages, | ||
requiredPerm: AppInstallPermissionNone, | ||
actualPerm: AppInstallPermissionNone, | ||
}, | ||
{ | ||
name: "read permission superseded by write", | ||
resource: AppInstallResourceContents, | ||
requiredPerm: AppInstallPermissionRead, | ||
actualPerm: AppInstallPermissionWrite, | ||
}, | ||
{ | ||
name: "missing permission", | ||
resource: AppInstallResourceChecks, | ||
requiredPerm: AppInstallPermissionWrite, | ||
actualPerm: "", | ||
expectedError: true, | ||
}, | ||
{ | ||
name: "invalid required permission", | ||
resource: AppInstallResourcePackages, | ||
requiredPerm: "invalid_perm", | ||
actualPerm: AppInstallPermissionRead, | ||
expectedError: true, | ||
}, | ||
{ | ||
name: "insufficient permission", | ||
resource: AppInstallResourceContents, | ||
requiredPerm: AppInstallPermissionWrite, | ||
actualPerm: AppInstallPermissionRead, | ||
expectedError: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := InstallationHasPermission(tt.resource, tt.requiredPerm, tt.actualPerm) | ||
if (err != nil) != tt.expectedError { | ||
t.Errorf("InstallationHasPermission() error = %v, expectedError %v", err, tt.expectedError) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func comparePermissions(a, b *github.InstallationPermissions) bool { | ||
if a == nil || b == nil { | ||
return a == b | ||
} | ||
return github.Stringify(a) == github.Stringify(b) | ||
} |
Oops, something went wrong.