Skip to content

Commit

Permalink
Merge pull request #14 from helaili/org-permission
Browse files Browse the repository at this point in the history
Org permission value in folder
  • Loading branch information
helaili authored Jun 14, 2023
2 parents ed0b237 + cfacf99 commit 05e8f4d
Show file tree
Hide file tree
Showing 11 changed files with 47 additions and 53 deletions.
33 changes: 21 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ This configuration will grant `write` access to the `codespace-oddity` repositor
- `repositories` when following an `owners` defininition (i.e. `.../owners/major-tom/repositories/starman`): each subfolder defines the name of a repository (within the organization or user) owning a worflow that can request a scoped token.
- `repositories` when not following an `owners` definition: each subfolder defines the name of the repository within the organization where the app is installed which will be accessible with the scoped token. The app needs to have access to the repository in order to be able to create a scoped token for it.
- `environments`: each subfolder defines the name of an environment (e.g. `development`, `production`, `staging`, etc.) targeted by the job requesting the scoped token.
- `organization`: each subfolder defines the name of an organization level permission that will be granted to the scoped token. The name of the folder will be used to build the name of the permission. For example, if the folder is named `administration`, the permission will be `organization_administration`. See the the `properties of permissions` section [here](https://docs.github.com/en/enterprise-cloud@latest/rest/apps/apps?apiVersion=2022-11-28#create-a-scoped-access-token) to see the list of permissions and their values.
- `organization`: each subfolder defines the name of an organization level permission that will be granted to the scoped token. The name of the folder will be used to build the name of the permission. For example, if the folder is named `administration`, the permission will be `organization_administration`. The subfolder will provide the value of the permission, so it should be one of these values: `read`, `write`, `admin`. See the the `properties of permissions` section [here](https://docs.github.com/en/enterprise-cloud@latest/rest/apps/apps?apiVersion=2022-11-28#create-a-scoped-access-token) to see the list of permissions and their values.

Notes:
- A file at the root of the repo can defined any permissions for any claim.
Expand All @@ -169,16 +169,17 @@ Sample folder hierarchy:
repo/
├─ organization/
│ ├─ administration/
│ │ ├─ owners/
│ │ │ ├─ ziggy-stardust/
│ │ │ ├─ major-tom/
│ │ │ │ ├─ environments/
│ │ │ │ │ ├─ development/
│ │ │ │ │ ├─ production/
│ │ │ │ │ │ ├─ org-perm-major-tom-production.json
│ │ │ │ ├─ repositories/
│ │ │ │ │ ├─ starman/
│ │ │ │ │ │ ├─ org-perm-major-tom-starman.json
│ │ ├─ read/
│ │ │ ├─ owners/
│ │ │ │ ├─ ziggy-stardust/
│ │ │ │ ├─ major-tom/
│ │ │ │ │ ├─ environments/
│ │ │ │ │ │ ├─ development/
│ │ │ │ │ │ ├─ production/
│ │ │ │ │ │ │ ├─ org-perm-major-tom-production.json
│ │ │ │ │ ├─ repositories/
│ │ │ │ │ │ ├─ starman/
│ │ │ │ │ │ │ ├─ org-perm-major-tom-starman.json
│ ├─ custom_roles/
├─ repositories/
│ ├─ codespace-oddity/
Expand Down Expand Up @@ -239,7 +240,15 @@ Sample file content:
}
```

Sample file content for organization permission, e.g: `/organization/administration/entitlement.json`. `organization_administration` will be the only permission set, any other permission will be ignored.
Sample file content for organization permission, e.g: `/organization/administration/read/entitlement.json`. `organization_administration` will be the only permission set, any other permission will be ignored.

```json
{
"workflow": "My first worlflow"
}
```

The previous would be equivalent to the following `/entitlement.json` file:

```json
{
Expand Down
36 changes: 12 additions & 24 deletions entitlementConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/golang-jwt/jwt/v5"
"github.com/google/go-github/v53/github"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

type EntitlementConfig struct {
Expand Down Expand Up @@ -101,28 +99,15 @@ func (config *EntitlementConfig) load(appTransport *ghinstallation.AppsTransport
/*
* Strip all the permissions but the one matching the one defined by the folder name .e.g. organization/<permissionName>
*/
func (config *EntitlementConfig) stripAllPermissionsBut(permissionName string, entitlement *Entitlement) {
// Need to turn the JSON field name into the object property name, e.g. organization_administration into OrganizationAdministration
permissionNameTokens := strings.Split(permissionName, "_")
objectPropertyName := ""
for _, token := range permissionNameTokens {
// Capitalize the first letter of the token
objectPropertyName += cases.Title(language.English).String(token)
}

// Get all the fields of the Permissions struct
fields := reflect.VisibleFields(reflect.TypeOf(struct{ github.InstallationPermissions }{}))
// Get all the permissions set in the entitlement
reflectScope := reflect.ValueOf(&entitlement.Scopes.Permissions).Elem()
func (config *EntitlementConfig) stripAllPermissionsBut(permissionName string, permission string, entitlement *Entitlement) {
jsonString := fmt.Sprintf(`{"%s": "%s"}`, permissionName, permission)
permissionObj := github.InstallationPermissions{}

for _, field := range fields {
value := reflectScope.FieldByName(field.Name)

// Has this filed been set? If it's not the one we want to keep, set it to zero
if value.IsValid() && !value.IsZero() && field.Name != objectPropertyName {
value.Set(reflect.Zero(value.Type()))
}
err := json.Unmarshal([]byte(jsonString), &permissionObj)
if err != nil {
log.Printf("failed to create permission %s", jsonString)
}
entitlement.Scopes.Permissions = permissionObj
}

/*
Expand Down Expand Up @@ -155,7 +140,7 @@ func (config *EntitlementConfig) loadFolder(path string, files []fs.DirEntry, is
// Regex to find the section right after /owners/ in the path
envRegex := regexp.MustCompile(`.*\/environments\/([^\/]+)\/`)
// Regex to find the section right after /organization/ in the path
orgRegex := regexp.MustCompile(`.*\/organization\/([^\/]+)\/`)
orgRegex := regexp.MustCompile(`.*\/organization\/([^\/]+)\/(read|admin|write)\/`)

skipFiles := false
// the directories below are not supposed to contain entitlement files
Expand Down Expand Up @@ -214,7 +199,10 @@ func (config *EntitlementConfig) loadFolder(path string, files []fs.DirEntry, is
orgPermissionName := orgRegex.FindStringSubmatch(fullPath)
if orgPermissionName != nil {
// we are under the orgnization/<permission> folder, so we can use the folder name as the unique permission name
config.stripAllPermissionsBut(fmt.Sprintf("organization_%s", orgPermissionName[1]), &entitlement)
config.stripAllPermissionsBut(fmt.Sprintf("organization_%s", orgPermissionName[1]), orgPermissionName[2], &entitlement)
// Whatever repo access needs to be removed
entitlement.Scopes.Repositories = nil

} else if !isRoot {
// We are not under the orgnization/<permission> folder and not at the root, so we need to strip all organization permissions
config.stripAllOrgPermissions(&entitlement)
Expand Down
2 changes: 1 addition & 1 deletion entitlementConfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestStripAllPermissionsBut(t *testing.T) {
},
}

config.stripAllPermissionsBut("organization_administration", &srcEntitlement)
config.stripAllPermissionsBut("organization_administration", "read", &srcEntitlement)

if !reflect.DeepEqual(expectedEntitlement, srcEntitlement) {
expectedEntitlementJson, _ := json.MarshalIndent(expectedEntitlement, "", " ")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"workflow": "Workflow 1",
"scopes": {
"repositories": [
"codespace-oddity"
],
"permissions": {
"contents": "write"
}
}
}
Empty file.

This file was deleted.

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}

0 comments on commit 05e8f4d

Please sign in to comment.