entitlements-github-plugin 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f3e4d8ab46a20943aefe27a5696b328fe1b793424d62bbdea0794fc303567be
4
- data.tar.gz: 29364fce9fbccf6a72c66d323bcc5d0860f0e1ea840fe1e97189c88cc29f18b8
3
+ metadata.gz: c768cc8f03a6eae9aaf1350173a59274b53d51cb38b7df20e798ea2e49b049e9
4
+ data.tar.gz: 1d66f2116e098a02cd81ca0f9a50c98995cda306ff3aceba0e2f37fc705f3b93
5
5
  SHA512:
6
- metadata.gz: e43bb1a57382d57baa89321c4d45bf20d6620f37f08a4f0699ab662f745e831e60f349221bdbe8936a69b72c6634fee6af42dbd2c31a4e7445deb5d3fb3807fb
7
- data.tar.gz: b76d0efc764efc7eb5f6886b7181285c036cf6f70f0cb9dd2a6db06ac32a9c5d1d883d371a6579abc460ba05654aa58071b415e03e5b69640c655dc734e6f83c
6
+ metadata.gz: 47b00156083eabe37d014eaccbca358c291e7c0ebd4d48f0da8225f347526fdd6cef79969ec152d14db258a8412dc4ab73b1aa35742cf904e135206c85cca51b
7
+ data.tar.gz: c9f604456245070f5b6ff2dd89b63a16bd36f052f8b489aad4c72b0d85e06782789b5697099053e0f2176a90884c303b5c75e28c9d5c4fb2944267619fde5e25
@@ -83,7 +83,7 @@ module Entitlements
83
83
  validate_no_dupes! # calls read() for each group
84
84
 
85
85
  if changes.any?
86
- print_differences(key: group_name, added: [], removed: [], changed: changes, ignored_users: ignored_users)
86
+ print_differences(key: group_name, added: [], removed: [], changed: changes, ignored_users:)
87
87
  @actions.concat(changes)
88
88
  else
89
89
  logger.debug "UNCHANGED: No GitHub organization changes for #{group_name}"
@@ -398,11 +398,11 @@ module Entitlements
398
398
  if removed.key?(member.downcase)
399
399
  # Already removed from a previous role. Therefore this is a move to a different role.
400
400
  removed.delete(member.downcase)
401
- moved[member.downcase] = { member: member, role: role }
401
+ moved[member.downcase] = { member:, role: }
402
402
  else
403
403
  # Not removed from a previous role. Suspect this is an addition to the org (if we later spot a removal
404
404
  # from a role, then the code below will update that to be a move instead).
405
- added[member.downcase] = { member: member, role: role }
405
+ added[member.downcase] = { member:, role: }
406
406
  end
407
407
  end
408
408
 
@@ -414,12 +414,12 @@ module Entitlements
414
414
  else
415
415
  # Not added to a previous role. Suspect this is a removal from the org (if we later spot an addition
416
416
  # to another role, then the code above will update that to be a move instead).
417
- removed[member.downcase] = { member: member, role: role }
417
+ removed[member.downcase] = { member:, role: }
418
418
  end
419
419
  end
420
420
  end
421
421
 
422
- { added: added, removed: removed, moved: moved }
422
+ { added:, removed:, moved: }
423
423
  end
424
424
 
425
425
  # Admins or members who are both `invited` and `pending` do not need to be re-invited. We're waiting for them
@@ -44,7 +44,7 @@ module Entitlements
44
44
  Contract String, String => C::Bool
45
45
  def add_user_to_organization(user, role)
46
46
  Entitlements.logger.debug "#{identifier} add_user_to_organization(user=#{user}, org=#{org}, role=#{role})"
47
- new_membership = octokit.update_organization_membership(org, user: user, role: role)
47
+ new_membership = octokit.update_organization_membership(org, user:, role:)
48
48
 
49
49
  # Happy path
50
50
  if new_membership[:role] == role
@@ -70,7 +70,7 @@ module Entitlements
70
70
  Contract String => C::Bool
71
71
  def remove_user_from_organization(user)
72
72
  Entitlements.logger.debug "#{identifier} remove_user_from_organization(user=#{user}, org=#{org})"
73
- result = octokit.remove_organization_membership(org, user: user)
73
+ result = octokit.remove_organization_membership(org, user:)
74
74
 
75
75
  # If we removed the user, remove them from the cache of members, so that any GitHub team
76
76
  # operations in this organization will ignore this user.
@@ -61,12 +61,12 @@ module Entitlements
61
61
  end
62
62
 
63
63
  if diff[:metadata] && diff[:metadata][:create_team]
64
- added << Entitlements::Models::Action.new(team_slug, provider.read(group), group, group_name, ignored_users: ignored_users)
64
+ added << Entitlements::Models::Action.new(team_slug, provider.read(group), group, group_name, ignored_users:)
65
65
  else
66
- changed << Entitlements::Models::Action.new(team_slug, provider.read(group), group, group_name, ignored_users: ignored_users)
66
+ changed << Entitlements::Models::Action.new(team_slug, provider.read(group), group, group_name, ignored_users:)
67
67
  end
68
68
  end
69
- print_differences(key: group_name, added: added, removed: [], changed: changed)
69
+ print_differences(key: group_name, added:, removed: [], changed:)
70
70
 
71
71
  @actions = added + changed
72
72
  end
@@ -27,7 +27,7 @@ module Entitlements
27
27
  @team_id = team_id
28
28
  @team_name = team_name.downcase
29
29
  @team_dn = ["cn=#{team_name.downcase}", ou].join(",")
30
- super(dn: @team_dn, members: Set.new(members.map { |m| m.downcase }), metadata: metadata)
30
+ super(dn: @team_dn, members: Set.new(members.map { |m| m.downcase }), metadata:)
31
31
  end
32
32
  end
33
33
  end
@@ -127,7 +127,7 @@ module Entitlements
127
127
  # Create the new team and invalidate the cache
128
128
  if github_team.nil?
129
129
  team_name = entitlement_group.cn.downcase
130
- github.create_team(entitlement_group: entitlement_group)
130
+ github.create_team(entitlement_group:)
131
131
  github.invalidate_predictive_cache(entitlement_group)
132
132
  @github_team_cache.delete(team_name)
133
133
  github_team = github.read_team(entitlement_group)
@@ -168,7 +168,7 @@ module Entitlements
168
168
  team_name: entitlement_group.cn.downcase,
169
169
  members: Set.new,
170
170
  ou: github.ou,
171
- metadata: metadata
171
+ metadata:
172
172
  )
173
173
  end
174
174
 
@@ -4,6 +4,7 @@ require_relative "models/team"
4
4
  require_relative "../../service/github"
5
5
 
6
6
  require "base64"
7
+ require "set"
7
8
 
8
9
  module Entitlements
9
10
  class Backend
@@ -77,7 +78,7 @@ module Entitlements
77
78
  team_id: -1,
78
79
  team_name: team_identifier,
79
80
  members: cached_members,
80
- ou: ou,
81
+ ou:,
81
82
  metadata: team_metadata
82
83
  )
83
84
 
@@ -107,11 +108,11 @@ module Entitlements
107
108
  team_id: teamdata[:team_id],
108
109
  team_name: team_identifier,
109
110
  members: Set.new(teamdata[:members]),
110
- ou: ou,
111
+ ou:,
111
112
  metadata: team_metadata
112
113
  )
113
114
  rescue TeamNotFound
114
- Entitlements.logger.warn "Team #{team_identifier} does not exist in this GitHub.com organization"
115
+ Entitlements.logger.warn "Team #{team_identifier} does not exist in this GitHub.com organization. If applied, the team will be created."
115
116
  return nil
116
117
  end
117
118
 
@@ -196,14 +197,56 @@ module Entitlements
196
197
  end
197
198
  end
198
199
 
199
- added_members = desired_state.member_strings.map { |u| u.downcase } - current_state.member_strings.map { |u| u.downcase }
200
- removed_members = current_state.member_strings.map { |u| u.downcase } - desired_state.member_strings.map { |u| u.downcase }
200
+ desired_team_members = Set.new(desired_state.member_strings.map { |u| u.downcase })
201
+ current_team_members = Set.new(current_state.member_strings.map { |u| u.downcase })
202
+
203
+ added_members = desired_team_members - current_team_members
204
+ removed_members = current_team_members - desired_team_members
201
205
 
202
206
  added_members.select! { |username| add_user_to_team(user: username, team: current_state) }
203
207
  removed_members.select! { |username| remove_user_from_team(user: username, team: current_state) }
204
208
 
209
+ added_maintainers = Set.new
210
+ removed_maintainers = Set.new
211
+ unless desired_metadata["team_maintainers"] == current_metadata["team_maintainers"]
212
+ if desired_metadata["team_maintainers"].nil?
213
+ # We will not delete ALL maintainers from a team and leave it without maintainers.
214
+ Entitlements.logger.debug "sync_team(#{current_state.team_name}=#{current_state.team_id}): IGNORING GitHub Team Maintainer DELETE"
215
+ else
216
+ desired_maintainers_str = desired_metadata["team_maintainers"] # not nil, we tested that above
217
+ desired_maintainers = Set.new(desired_maintainers_str.split(",").map { |u| u.strip.downcase })
218
+ unless desired_maintainers.subset?(desired_team_members)
219
+ maintainer_not_member = desired_maintainers - desired_team_members
220
+ Entitlements.logger.warn "sync_team(#{current_state.team_name}=#{current_state.team_id}): Maintainers must be a subset of team members. Desired maintainers: #{maintainer_not_member.to_a} are not members. Ignoring."
221
+ desired_maintainers = desired_maintainers.intersection(desired_team_members)
222
+ end
223
+
224
+ current_maintainers_str = current_metadata["team_maintainers"]
225
+ current_maintainers = Set.new(
226
+ current_maintainers_str.nil? ? [] : current_maintainers_str.split(",").map { |u| u.strip.downcase }
227
+ )
228
+ # We ignore any current maintainer who is not a member of the team according to the team spec
229
+ # This avoids messing with teams who have been manually modified to add a maintainer
230
+ current_maintainers = current_maintainers.intersection(desired_team_members)
231
+ added_maintainers = desired_maintainers - current_maintainers
232
+ removed_maintainers = current_maintainers - desired_maintainers
233
+ if added_maintainers.empty? && removed_maintainers.empty?
234
+ Entitlements.logger.debug "sync_team(#{current_state.team_name}=#{current_state.team_id}): Textual change but no semantic change in maintainers. It is remains: #{current_maintainers.to_a}."
235
+ else
236
+ Entitlements.logger.debug "sync_team(#{current_state.team_name}=#{current_state.team_id}): Maintainer members change found - From #{current_maintainers.to_a} to #{desired_maintainers.to_a}"
237
+ added_maintainers.select! { |username| add_user_to_team(user: username, team: current_state, role: "maintainer") }
238
+
239
+ ## We only touch previous maintainers who are actually still going to be members of the team
240
+ removed_maintainers = removed_maintainers.intersection(desired_team_members)
241
+ ## Downgrade membership to default (role: "member")
242
+ removed_maintainers.select! { |username| add_user_to_team(user: username, team: current_state, role: "member") }
243
+ end
244
+ end
245
+ end
246
+
247
+
205
248
  Entitlements.logger.debug "sync_team(#{current_state.team_name}=#{current_state.team_id}): Added #{added_members.count}, removed #{removed_members.count}"
206
- added_members.any? || removed_members.any? || changed_parent_team
249
+ added_members.any? || removed_members.any? || added_maintainers.any? || removed_maintainers.any? || changed_parent_team
207
250
  end
208
251
 
209
252
  # Create a team
@@ -337,7 +380,7 @@ module Entitlements
337
380
  break
338
381
  end
339
382
 
340
- { members: result, team_id: team_id, parent_team_name: parent_team_name }
383
+ { members: result, team_id:, parent_team_name: }
341
384
  end
342
385
 
343
386
  # Ensure that the given team ID actually matches up to the team slug on GitHub. This is in place
@@ -366,17 +409,23 @@ module Entitlements
366
409
  #
367
410
  # user - String with the GitHub username
368
411
  # team - Entitlements::Backend::GitHubTeam::Models::Team object for the team.
412
+ # role - optional (default: "member") String with the role to assign to the user: either "member" or "maintainer"
369
413
  #
370
- # Returns true if the user was added to the team, false if user was already on team.
414
+ # Returns true if the user was added to the team or role changed; false if user was already on team with same role
371
415
  Contract C::KeywordArgs[
372
416
  user: String,
373
417
  team: Entitlements::Backend::GitHubTeam::Models::Team,
418
+ role: C::Optional[String]
374
419
  ] => C::Bool
375
- def add_user_to_team(user:, team:)
420
+ def add_user_to_team(user:, team:, role: "member")
376
421
  return false unless org_members.include?(user.downcase)
377
- Entitlements.logger.debug "#{identifier} add_user_to_team(user=#{user}, org=#{org}, team_id=#{team.team_id})"
422
+ unless role == "member" || role == "maintainer"
423
+ # :nocov:
424
+ raise "add_user_to_team role mismatch: team_id=#{team.team_id} user=#{user} expected role=maintainer/member got=#{role}"
425
+ end
426
+ Entitlements.logger.debug "#{identifier} add_user_to_team(user=#{user}, org=#{org}, team_id=#{team.team_id}, role=#{role})"
378
427
  validate_team_id_and_slug!(team.team_id, team.team_name)
379
- result = octokit.add_team_membership(team.team_id, user)
428
+ result = octokit.add_team_membership(team.team_id, user, role:)
380
429
  result[:state] == "active" || result[:state] == "pending"
381
430
  end
382
431
 
@@ -82,7 +82,7 @@ module Entitlements
82
82
  member_count = result.count { |_, role| role == "member" }
83
83
  Entitlements.logger.debug "Currently #{org} has #{admin_count} admin(s) and #{member_count} member(s)"
84
84
 
85
- { cache: cache, value: result }
85
+ { cache:, value: result }
86
86
  end
87
87
 
88
88
  Entitlements.cache[:github_org_members][org_signature][:value]
@@ -354,9 +354,9 @@ module Entitlements
354
354
  data = JSON.parse(response.body)
355
355
  if data.key?("errors")
356
356
  Entitlements.logger.error "Errors reported: #{data['errors'].inspect}"
357
- return { code: 500, data: data }
357
+ return { code: 500, data: }
358
358
  end
359
- { code: response.code.to_i, data: data }
359
+ { code: response.code.to_i, data: }
360
360
  rescue JSON::ParserError => e
361
361
  Entitlements.logger.error "#{e.class} #{e.message}: #{response.body.inspect}"
362
362
  { code: 500, data: { "body" => response.body } }
data/lib/version.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Entitlements
4
+ module Version
5
+ VERSION = "0.4.0"
6
+ end
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: entitlements-github-plugin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub, Inc. Security Ops
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-14 00:00:00.000000000 Z
11
+ date: 2023-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: contracts
@@ -234,13 +234,12 @@ dependencies:
234
234
  - - '='
235
235
  - !ruby/object:Gem::Version
236
236
  version: 3.4.2
237
- description: ''
237
+ description: Entitlements plugin to manage GitHub Orgs and Team memberships and access
238
238
  email: security@github.com
239
239
  executables: []
240
240
  extensions: []
241
241
  extra_rdoc_files: []
242
242
  files:
243
- - VERSION
244
243
  - lib/entitlements/backend/github_org.rb
245
244
  - lib/entitlements/backend/github_org/controller.rb
246
245
  - lib/entitlements/backend/github_org/provider.rb
@@ -251,11 +250,12 @@ files:
251
250
  - lib/entitlements/backend/github_team/provider.rb
252
251
  - lib/entitlements/backend/github_team/service.rb
253
252
  - lib/entitlements/service/github.rb
253
+ - lib/version.rb
254
254
  homepage: https://github.com/github/entitlements-github-plugin
255
255
  licenses:
256
256
  - MIT
257
257
  metadata: {}
258
- post_install_message:
258
+ post_install_message:
259
259
  rdoc_options: []
260
260
  require_paths:
261
261
  - lib
@@ -271,7 +271,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
271
271
  version: '0'
272
272
  requirements: []
273
273
  rubygems_version: 3.3.7
274
- signing_key:
274
+ signing_key:
275
275
  specification_version: 4
276
276
  summary: GitHub dotcom provider for entitlements-app
277
277
  test_files: []
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.2.0