public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { foreach (var repo in context.Org.Repos) { var isRepoOwnedByMicrosoft = repo.IsOwnedByMicrosoft(); if (isRepoOwnedByMicrosoft) { foreach (var userAccess in repo.Users.Where(ua => ua.Describe().IsCollaborator)) { var user = userAccess.User; var permission = userAccess.Permission; var userWorksForMicrosoft = context.IsMicrosoftUser(user); if (!userWorksForMicrosoft && permission != CachedPermission.Pull) { yield return(new PolicyViolation( Descriptor, title: $"Non-Microsoft contributor '{user.Login}' should only have 'pull' permission for '{repo.Name}'", body: $@" The non-Microsoft contributor {user.Markdown()} was granted {permission.Markdown()} for the Microsoft-owned repo {repo.Markdown()}. Only Microsoft users should have more than `pull` permissions. * If this is a Microsoft user, they need to [link](https://docs.opensource.microsoft.com/tools/github/accounts/linking.html) their account. * If this isn't a Microsoft user, their permission needs to be changed to `pull`. ", org: context.Org, repo: repo, user: user )); } } } } ; }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { var allowedPermission = CachedPermission.Pull; var microsoftTeam = context.Org.GetMicrosoftTeam(); foreach (var team in context.Org.Teams) { var isOwnedByMicrosoft = team.IsOwnedByMicrosoft(); var grantsMoreThanPullAccessToMicrosoftRepo = team.Repos.Any(r => r.Permission != allowedPermission && r.Repo.IsOwnedByMicrosoft());; if (!isOwnedByMicrosoft && grantsMoreThanPullAccessToMicrosoftRepo) { yield return(new PolicyViolation( Descriptor, title: $"Team '{team.Name}' must be owned by Microsoft", body: $@" Team {team.Markdown()} grants at least one Microsoft-owned repo more than {allowedPermission.Markdown()} permissions. The team must be owned by Microsoft. To indicate that the team is owned by Microsoft, ensure that one of the parent teams is {microsoftTeam.Markdown()}. ", org: context.Org, team: team )); } } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { foreach (var team in context.Org.Teams) { var isOwnedByMicrosoft = team.IsOwnedByMicrosoft(); if (isOwnedByMicrosoft) { foreach (var user in team.Members) { var isMicrosoftUser = context.IsMicrosoftUser(user); if (!isMicrosoftUser) { yield return(new PolicyViolation( Descriptor, title: $"Microsoft owned team '{team.Name}' shouldn't contain '{user.Login}'", body: $@" Microsoft owned team {team.Markdown()} shouldn't contain user {user.Markdown()} because they are not an employee. * If this is a Microsoft user, they need to [link](https://docs.opensource.microsoft.com/tools/github/accounts/linking.html) their account. * If this team is supposed to represent Microsoft and non-Microsoft, the team shouldn't be owned by Microsoft * If this isn't a Microsoft user, they need to be removed from this team. ", org: context.Org, team: team, user: user )); } } } } ; }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { var botsTeam = context.Org.GetBotsTeam(); if (botsTeam == null) { yield break; } foreach (var user in context.Org.Users) { var isKnownBot = user.IsBot(); var isPotentiallyABot = user.IsPotentiallyABot(); if (!isKnownBot && isPotentiallyABot) { yield return(new PolicyViolation( Descriptor, title: $"User '{user.Login}' should be marked as a bot", body: $@" The user {user.Markdown()} appears to be a bot. * If this is in fact a human, mark this issue as `policy-override` and close the issue * If this is a bot, add it to the team {botsTeam.Markdown()} ", org: context.Org, team: botsTeam, user: user )); } } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { foreach (var repo in context.Org.Repos) { foreach (var teamAccess in repo.Teams) { var team = teamAccess.Team; if (team.IsMarkerTeam() && teamAccess.Permission != CachedPermission.Pull) { yield return(new PolicyViolation( Descriptor, title: $"Repo '{repo.Name}' should only grant '{team.Name}' with 'pull' permissions", body: $@" The marker team {team.Markdown()} is only used to indicate ownership. It should only ever grant `pull` permissions. Change the permissions for {team.Markdown()} in repo {repo.Markdown()} to `pull`. ", org: context.Org, repo: repo, team: team )); } } } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { foreach (var repo in context.Org.Repos) { if (!repo.IsOwnedByMicrosoft()) { continue; } var adminTeams = repo.Teams.Where(ta => ta.Permission == CachedPermission.Admin) .Select(ta => ta.Team); var recommendation = string.Join(", ", adminTeams.Select(a => a.Markdown())); if (recommendation.Length > 0) { recommendation = "Consider adding the user to one of these teams: " + recommendation + "."; } else { recommendation = "Grant a team `admin` permissions for this repo and ensure the user is in that team."; } foreach (var userAccess in repo.Users) { var user = userAccess.User; var isAdmin = userAccess.Permission == CachedPermission.Admin; var isDirectlyAssigned = userAccess.Describe().IsCollaborator; if (isAdmin && isDirectlyAssigned) { yield return(new PolicyViolation( Descriptor, title: $"Admin access for '{user.Login}' in repo '{repo.Name}' should be granted via a team", body: $@" The user {user.Markdown()} shouldn't be directly added as an admin for repo {repo.Markdown()}. Instead, the user should be in a team that is granted `admin` permissions. {recommendation} ", org: context.Org, repo: repo, user: user )); } } } }
private static async Task RunAsync(string orgName, string outputFileName, string cacheLocation, string githubToken, string ospoToken, string policyRepo) { var isForExcel = outputFileName == null; var gitHubClient = await GitHubClientFactory.CreateAsync(githubToken); var ospoClient = await OspoClientFactory.CreateAsync(ospoToken); var cachedOrg = await CachedOrg.LoadAsync(gitHubClient, orgName, Console.Out, cacheLocation, forceUpdate : false); var userLinks = await MicrosoftUserLinks.LoadAsync(ospoClient); var context = new PolicyAnalysisContext(cachedOrg, userLinks); var violations = PolicyRunner.Run(context); SaveVioloations(orgName, outputFileName, isForExcel, violations); if (!string.IsNullOrEmpty(policyRepo)) { await FilePolicyViolationsAsync(gitHubClient, orgName, policyRepo, violations); } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { foreach (var team in context.Org.Teams) { var hasChildren = team.Children.Any(); var hasRepos = team.Repos.Any(); var isUsed = hasChildren || hasRepos; if (!isUsed) { yield return(new PolicyViolation( Descriptor, title: $"Unused team '{team.Name}' should be removed", body: $@" Team {team.Markdown()} doesn't have any associated repos nor nested teams. It should either be used or removed. ", org: context.Org, team: team )); } } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { const int Threshold = 4; foreach (var team in context.Org.Teams) { var numberOfMaintainers = team.Maintainers.Count; if (numberOfMaintainers > Threshold) { yield return(new PolicyViolation( Descriptor, title: $"Team '{team.Name}' has too many maintainers", body: $@" The team {team.Markdown()} has {numberOfMaintainers} maintainers. Reduce the number of maintainers to {Threshold} or less. ", org: context.Org, team: team )); } } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { var now = DateTimeOffset.Now; var threshold = TimeSpan.FromDays(365); foreach (var repo in context.Org.Repos) { var alreadyArchived = repo.IsArchived; var inactivity = now - repo.LastPush; if (!alreadyArchived && inactivity > threshold) { yield return(new PolicyViolation( Descriptor, title: $"Inactive repo '{repo.Name}' should be archived", body: $@" The last push to repo {repo.Markdown()} is more than {threshold.TotalDays:N0} days ago. It should be archived. ", org: context.Org, repo: repo )); } } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { const int Threshold = 4; foreach (var repo in context.Org.Repos) { var numberOfAdmins = repo.Users.Count(ua => ua.Permission == CachedPermission.Admin && !ua.Describe().IsOwner); if (numberOfAdmins > Threshold) { yield return(new PolicyViolation( Descriptor, title: $"Repo '{repo.Name}' has too many admins", body: $@" The repo {repo.Markdown()} has {numberOfAdmins} admins. Reduce the number of admins to {Threshold} or less. ", org: context.Org, repo: repo )); } } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { foreach (var user in context.Org.Users) { var userClaimsToBeWorkingForMicrosoft = user.IsClaimingToBeWorkingForMicrosoft(); var isMicrosoftUser = context.IsMicrosoftUser(user); if (userClaimsToBeWorkingForMicrosoft && !isMicrosoftUser) { yield return(new PolicyViolation( Descriptor, title: $"Microsoft-user '{user.Login}' should be linked", body: $@" User {user.Markdown()} appears to be a Microsoft employee. They should be [linked](https://opensource.microsoft.com/link) to a Microsoft account. For more details, see [documentation](https://docs.opensource.microsoft.com/tools/github/accounts/linking.html). ", org: context.Org, user: user )); } } }
public override IEnumerable <PolicyViolation> GetViolations(PolicyAnalysisContext context) { const int Threshold = 2; foreach (var repo in context.Org.Repos) { var isArchived = repo.IsArchived; var numberOfAdmins = repo.Users.Count(ua => ua.Permission == CachedPermission.Admin && !ua.Describe().IsOwner); if (!isArchived && numberOfAdmins < Threshold) { yield return(new PolicyViolation( Descriptor, title: $"Repo '{repo.Name}' needs more admins", body: $@" The repo {repo.Markdown()} has {numberOfAdmins} admins (excluding organization owners). It is recommended to have at least {Threshold} admins. ", org: context.Org, repo: repo )); } } }