ephemeris_forge_github/plan/
access.rs1use ephemeris_forge::{
2 Access, AccessDecision, AccessOperation, CollaboratorDecision, Limitation, PermissionScope,
3 Report, TeamName,
4};
5
6use crate::api::model::OwnerKind;
7use crate::operation::access::Operation;
8use crate::{Live, Permission};
9
10pub(super) fn plan(
11 config: Option<&Access<Permission>>,
12 live: &Live,
13 report: &mut Report,
14) -> Vec<Operation> {
15 let Some(access) = config else {
16 return Vec::new();
17 };
18 let personal = live.owner == Some(OwnerKind::User);
19 let grants = live.access.grants(PermissionScope::Repository);
20 let mut operations = Vec::new();
21 for decision in access.plan(&grants) {
22 if personal && let Some((team, role)) = custom_team(&decision) {
23 report
24 .limitations
25 .push(Limitation::TeamRoleNeedsOrganization {
26 team: team.clone(),
27 role: role.as_str().to_owned(),
28 });
29 continue;
30 }
31 decision.record(report, "GitHub");
32 operations.extend(team_operation(decision));
33 }
34 let mut present = live.access.collaborators.clone();
35 present.extend(
36 live.invitations
37 .iter()
38 .map(|(user, invitation)| (user.clone(), invitation.permission.clone())),
39 );
40 for decision in access.plan_collaborators(&present, live.access.caller.as_ref()) {
41 if personal && let Some((user, role)) = custom_collaborator(&decision) {
42 report
43 .limitations
44 .push(Limitation::CollaboratorRoleNeedsOrganization {
45 user: user.clone(),
46 role: role.as_str().to_owned(),
47 });
48 continue;
49 }
50 decision.record(report);
51 operations.extend(collaborator_operation(decision, live));
52 }
53 operations
54}
55
56fn custom_team(decision: &AccessDecision<Permission>) -> Option<(&TeamName, &Permission)> {
57 match decision {
58 AccessDecision::Add { team, permission }
59 | AccessDecision::PermissionMismatch {
60 team,
61 expected: permission,
62 actual: _,
63 }
64 | AccessDecision::OrganizationPermission {
65 team,
66 expected: permission,
67 actual: _,
68 } => permission.custom().map(|_role| (team, permission)),
69 AccessDecision::Remove { team: _ }
70 | AccessDecision::NotInOrganization { team: _ }
71 | AccessDecision::SelfRemoval { team: _ }
72 | AccessDecision::Matches { team: _ }
73 | AccessDecision::Inherited { team: _ } => None,
74 }
75}
76
77fn custom_collaborator(
78 decision: &CollaboratorDecision<Permission>,
79) -> Option<(&ephemeris_forge::UserName, &Permission)> {
80 match decision {
81 CollaboratorDecision::Add { user, permission }
82 | CollaboratorDecision::Change {
83 user,
84 permission,
85 actual: _,
86 } => permission.custom().map(|_role| (user, permission)),
87 CollaboratorDecision::Remove { user: _ }
88 | CollaboratorDecision::SelfRemoval { user: _ }
89 | CollaboratorDecision::Matches { user: _ } => None,
90 }
91}
92
93fn team_operation(decision: AccessDecision<Permission>) -> Option<Operation> {
94 match decision {
95 AccessDecision::Add { team, permission } => {
96 Some(Operation::Grant(AccessOperation::AddTeam {
97 team,
98 permission,
99 }))
100 }
101 AccessDecision::Remove { team } => {
102 Some(Operation::Grant(AccessOperation::RemoveTeam { team }))
103 }
104 AccessDecision::PermissionMismatch {
105 team,
106 expected,
107 actual: _,
108 } => Some(Operation::Grant(AccessOperation::AddTeam {
109 team,
110 permission: expected,
111 })),
112 AccessDecision::NotInOrganization { team: _ }
113 | AccessDecision::OrganizationPermission {
114 team: _,
115 expected: _,
116 actual: _,
117 }
118 | AccessDecision::Matches { team: _ }
119 | AccessDecision::SelfRemoval { team: _ }
120 | AccessDecision::Inherited { team: _ } => None,
121 }
122}
123
124fn collaborator_operation(
125 decision: CollaboratorDecision<Permission>,
126 live: &Live,
127) -> Option<Operation> {
128 match decision {
129 CollaboratorDecision::Add { user, permission } => {
130 Some(Operation::Grant(AccessOperation::AddCollaborator {
131 user,
132 permission,
133 }))
134 }
135 CollaboratorDecision::Change {
136 user,
137 permission,
138 actual: _,
139 } => Some(match live.invitations.get(&user) {
140 Some(invitation) if permission.invitation_name().is_some() => {
141 Operation::SetInvitationPermission {
142 id: invitation.id,
143 user,
144 permission,
145 }
146 }
147 Some(_) | None => {
148 Operation::Grant(AccessOperation::AddCollaborator { user, permission })
149 }
150 }),
151 CollaboratorDecision::Remove { user } => Some(match live.invitations.get(&user) {
152 Some(invitation) => Operation::CancelInvitation {
153 id: invitation.id,
154 user,
155 },
156 None => Operation::Grant(AccessOperation::RemoveCollaborator { user }),
157 }),
158 CollaboratorDecision::Matches { user: _ }
159 | CollaboratorDecision::SelfRemoval { user: _ } => None,
160 }
161}