Skip to content

Commit 0294114

Browse files
🚀[Feature]: Permission infrastructure + Track GitHub App Installation status (#513)
This pull request introduces several improvements to how GitHub App permissions are represented, compared, and displayed throughout the codebase. The main focus is on replacing the use of generic objects for permissions with a strongly-typed `GitHubPermission` class, adding installation status comparison logic, and enhancing output formatting for better user experience. - Fixes #510 Key changes include: **Permission Model Refactor:** * Replaces all uses of `[pscustomobject]` for permissions in `GitHubApp`, `GitHubAppInstallation`, `GitHubAppContext`, and `GitHubAppInstallationContext` with `[GitHubPermission[]]`, ensuring permissions are strongly typed and consistently handled. * Colocate `GitHubPermissionDefinition` class with the `GitHubPermission` in the `GitHubPermission.ps1` file. **Installation Status and Comparison Logic:** * Adds a `Status` property to `GitHubAppInstallation` and implements logic to compare installation permissions with app permissions, setting status as `'Ok'` or `'Outdated'` accordingly. * Updates `Get-GitHubAppInstallationForAuthenticatedApp` to pass the authenticated app object to each installation for accurate status calculation. **Formatting and Output Enhancements:** * Updates the `GitHubAppInstallation` format XML to display the new `Status` property in both table and list views, using symbols and colors when supported for better clarity. * Adds a new format file for `GitHubPermission`, improving how permissions are displayed in the console, including hyperlinks for permission names when supported. **Configuration and Miscellaneous:** * Makes the `Context` parameter mandatory and positional in `Switch-GitHubContext` for improved usability. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> Co-authored-by: Marius Storhaug <marstor@hotmail.com>
1 parent 0d0b600 commit 0294114

20 files changed

+1750
-1442
lines changed

‎src/classes/public/App/GitHubApp.ps1‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
[System.Nullable[datetime]] $UpdatedAt
3737

3838
# The permissions that the app is requesting.
39-
[pscustomobject] $Permissions
39+
[GitHubPermission[]] $Permissions
4040

4141
# The events that the app is subscribing to on its target.
4242
[string[]] $Events
@@ -59,7 +59,7 @@
5959
$this.Url = $Object.html_url
6060
$this.CreatedAt = $Object.created_at
6161
$this.UpdatedAt = $Object.updated_at
62-
$this.Permissions = $Object.permissions
62+
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions)
6363
$this.Events = , ($Object.events)
6464
$this.Installations = $Object.installations_count
6565
}

‎src/classes/public/App/GitHubAppInstallation.ps1‎

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
[string] $RepositorySelection
1616

1717
# The permissions that the app has on the target.
18-
[pscustomobject] $Permissions
18+
[GitHubPermission[]] $Permissions
1919

2020
# The events that the app is subscribing to.
2121
[string[]] $Events
@@ -41,6 +41,9 @@
4141
# The URL to the target's profile based on the target type.
4242
[string] $Url
4343

44+
# The status indicating if the installation permissions and events match the app's configuration.
45+
[string] $Status
46+
4447
GitHubAppInstallation() {}
4548

4649
GitHubAppInstallation([PSCustomObject] $Object) {
@@ -55,14 +58,32 @@
5558
$this.Target = [GitHubOwner]::new($Object.account)
5659
$this.Type = $Object.target_type
5760
$this.RepositorySelection = $Object.repository_selection
58-
$this.Permissions = $Object.permissions
61+
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type)
62+
$this.Events = , ($Object.events)
63+
$this.FilePaths = $Object.single_file_paths
64+
$this.CreatedAt = $Object.created_at
65+
$this.UpdatedAt = $Object.updated_at
66+
$this.SuspendedAt = $Object.suspended_at
67+
$this.SuspendedBy = [GitHubUser]::new($Object.suspended_by)
68+
$this.Url = $Object.html_url
69+
$this.Status = 'Unknown'
70+
}
71+
72+
GitHubAppInstallation([PSCustomObject] $Object, [GitHubApp] $App) {
73+
$this.ID = $Object.id
74+
$this.App = $App
75+
$this.Target = [GitHubOwner]::new($Object.account)
76+
$this.Type = $Object.target_type
77+
$this.RepositorySelection = $Object.repository_selection
78+
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type)
5979
$this.Events = , ($Object.events)
6080
$this.FilePaths = $Object.single_file_paths
6181
$this.CreatedAt = $Object.created_at
6282
$this.UpdatedAt = $Object.updated_at
6383
$this.SuspendedAt = $Object.suspended_at
6484
$this.SuspendedBy = [GitHubUser]::new($Object.suspended_by)
6585
$this.Url = $Object.html_url
86+
$this.UpdateStatus()
6687
}
6788

6889
GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [GitHubContext] $Context) {
@@ -81,13 +102,95 @@
81102
}
82103
$this.Type = $Type
83104
$this.RepositorySelection = $Object.repository_selection
84-
$this.Permissions = $Object.permissions
105+
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type)
106+
$this.Events = , ($Object.events)
107+
$this.FilePaths = $Object.single_file_paths
108+
$this.CreatedAt = $Object.created_at
109+
$this.UpdatedAt = $Object.updated_at
110+
$this.SuspendedAt = $Object.suspended_at
111+
$this.SuspendedBy = [GitHubUser]::new($Object.suspended_by)
112+
$this.Url = "https://$($Context.HostName)/$($Type.ToLower())s/$Target/settings/installations/$($Object.id)"
113+
$this.Status = 'Unknown'
114+
}
115+
116+
GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [GitHubContext] $Context, [GitHubApp] $App) {
117+
$this.ID = $Object.id
118+
$this.App = $App
119+
$this.Target = [GitHubOwner]@{
120+
Name = $Target
121+
Type = $Type
122+
Url = "https://$($Context.HostName)/$Target"
123+
}
124+
$this.Type = $Type
125+
$this.RepositorySelection = $Object.repository_selection
126+
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type)
85127
$this.Events = , ($Object.events)
86128
$this.FilePaths = $Object.single_file_paths
87129
$this.CreatedAt = $Object.created_at
88130
$this.UpdatedAt = $Object.updated_at
89131
$this.SuspendedAt = $Object.suspended_at
90132
$this.SuspendedBy = [GitHubUser]::new($Object.suspended_by)
91133
$this.Url = "https://$($Context.HostName)/$($Type.ToLower())s/$Target/settings/installations/$($Object.id)"
134+
$this.UpdateStatus()
135+
}
136+
137+
# Updates the Status property by comparing installation permissions with app permissions
138+
# filtered by the appropriate scope based on installation type
139+
[void] UpdateStatus() {
140+
if (-not $this.App -or -not $this.App.Permissions) {
141+
$this.Status = 'Unknown'
142+
return
143+
}
144+
145+
# Get app permissions filtered by installation type scope
146+
$appPermissionsFiltered = switch ($this.Type) {
147+
'Enterprise' {
148+
$this.App.Permissions | Where-Object { $_.Scope -eq 'Enterprise' }
149+
}
150+
'Organization' {
151+
$this.App.Permissions | Where-Object { $_.Scope -in @('Organization', 'Repository') }
152+
}
153+
'User' {
154+
$this.App.Permissions | Where-Object { $_.Scope -in @('Repository') }
155+
}
156+
default {
157+
$this.App.Permissions
158+
}
159+
}
160+
161+
# Compare permissions by creating lookup dictionaries
162+
$appPermissionLookup = @{}
163+
foreach ($perm in $appPermissionsFiltered) {
164+
$appPermissionLookup[$perm.Name] = $perm.Value
165+
}
166+
167+
$installationPermissionLookup = @{}
168+
foreach ($perm in $this.Permissions) {
169+
$installationPermissionLookup[$perm.Name] = $perm.Value
170+
}
171+
172+
# Check if permissions match
173+
$permissionsMatch = $true
174+
175+
# Check if all app permissions exist in installation with same values
176+
foreach ($name in $appPermissionLookup.Keys) {
177+
if (-not $installationPermissionLookup.ContainsKey($name) -or
178+
$installationPermissionLookup[$name] -ne $appPermissionLookup[$name]) {
179+
$permissionsMatch = $false
180+
break
181+
}
182+
}
183+
184+
# Check if installation has any extra permissions not in the app
185+
if ($permissionsMatch) {
186+
foreach ($name in $installationPermissionLookup.Keys) {
187+
if (-not $appPermissionLookup.ContainsKey($name)) {
188+
$permissionsMatch = $false
189+
break
190+
}
191+
}
192+
}
193+
194+
$this.Status = $permissionsMatch ? 'Ok' : 'Outdated'
92195
}
93196
}

‎src/classes/public/Context/GitHubContext/GitHubAppContext.ps1‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
[string] $OwnerType
1616

1717
# The permissions that the app is requesting on the target
18-
[pscustomobject] $Permissions
18+
[GitHubPermission[]] $Permissions
1919

2020
# The events that the app is subscribing to once installed
2121
[string[]] $Events
@@ -47,7 +47,7 @@
4747
$this.KeyVaultKeyReference = $Object.KeyVaultKeyReference
4848
$this.OwnerName = $Object.OwnerName
4949
$this.OwnerType = $Object.OwnerType
50-
$this.Permissions = $Object.Permissions
50+
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.Permissions)
5151
$this.Events = $Object.Events
5252
}
5353
}

‎src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[System.Nullable[uint64]] $InstallationID
77

88
# The permissions that the app is requesting on the target
9-
[pscustomobject] $Permissions
9+
[GitHubPermission[]] $Permissions
1010

1111
# The events that the app is subscribing to once installed
1212
[string[]] $Events
@@ -41,7 +41,7 @@
4141
$this.PerPage = $Object.PerPage
4242
$this.ClientID = $Object.ClientID
4343
$this.InstallationID = $Object.InstallationID
44-
$this.Permissions = $Object.Permissions
44+
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.Permissions, $Object.InstallationType)
4545
$this.Events = $Object.Events
4646
$this.InstallationType = $Object.InstallationType
4747
$this.InstallationName = $Object.InstallationName

0 commit comments

Comments
 (0)