Skip to content

Commit

Permalink
Merge pull request #1170 from Avanade/1162-scan-community-organizations
Browse files Browse the repository at this point in the history
feat: Scan Community Organizations
  • Loading branch information
iibuan authored Oct 4, 2024
2 parents 5e36d93 + ce12402 commit 8af8833
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 60 deletions.
1 change: 1 addition & 0 deletions .bicep/logicapps/parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
},
"appSettings": {
"value": {
"Workflows.CommunityOrganizationsScan.FlowState": "Disabled",
"Workflows.CleanupOrganization.FlowState" : "Disabled",
"Workflows.ExpiringInvitation.FlowState": "Disabled",
"Workflows.IndexOrgRepos.FlowState": "Disabled",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"actions": {
"HTTP_CommunityOrganizationsScan": {
"inputs": {
"authentication": {
"identity": "@concat('/subscriptions/59d64684-e7c9-4397-8982-6b775a473b74/resourcegroups/OpenTech_GitHub_Mgmt/providers/Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('ManagedIdentityName'))",
"type": "ManagedServiceIdentity"
},
"method": "GET",
"uri": "@{concat(parameters('GHMgmDomain'), '/utility/scan-community-organizations')}"
},
"runAfter": {},
"type": "Http"
}
},
"contentVersion": "1.0.0.0",
"outputs": {},
"triggers": {
"Recurrence": {
"recurrence": {
"frequency": "Week",
"interval": 1,
"schedule": {
"weekDays": [
"Sunday"
]
}
},
"type": "Recurrence"
}
}
},
"kind": "Stateful"
}
1 change: 1 addition & 0 deletions .github/workflows/setup-logicapp-resource.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
files: ./.bicep/logicapps/parameters.json
env:
parameters.env.value: ${{ vars.HOME_URL }}
parameters.appsettings.value.Workflows.CommunityOrganizationsScan.FlowState: ${{ vars.WORKFLOWS_COMMUNITY_ORGANIZATIONS_SCAN_FLOWSTATE }}
parameters.appSettings.value.Workflows.CleanupOrganization.FlowState: ${{ vars.WORKFLOWS_CLEANUP_ORGANIZATION_FLOWSTATE }}
parameters.appSettings.value.Workflows.ExpiringInvitation.FlowState: ${{ vars.WORKFLOWS_EXPIRING_INVITATION_FLOWSTATE }}
parameters.appSettings.value.Workflows.IndexOrgRepos.FlowState: ${{ vars.WORKFLOWS_INDEX_ORG_REPOS_FLOWSTATE }}
Expand Down
6 changes: 6 additions & 0 deletions src/goapp/pkg/ghmgmtdb/regionalOrganizationDb.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
type RegionalOrganization struct {
Id int64
Name string
IsRegionalOrganization bool
IsIndexRepoEnabled bool
IsCopilotRequestEnabled bool
IsAccessRequestEnabled bool
Expand Down Expand Up @@ -41,6 +42,7 @@ func SelectRegionalOrganization(isEnabled *NullBool) ([]RegionalOrganization, er
organization := RegionalOrganization{
Id: row["Id"].(int64),
Name: row["Name"].(string),
IsRegionalOrganization: row["IsRegionalOrganization"].(bool),
IsIndexRepoEnabled: row["IsIndexRepoEnabled"].(bool),
IsCopilotRequestEnabled: row["IsCopilotRequestEnabled"].(bool),
IsAccessRequestEnabled: row["IsAccessRequestEnabled"].(bool),
Expand Down Expand Up @@ -87,6 +89,7 @@ func SelectRegionalOrganizationByOption(offset, filter int64, search, orderBy, o
organization := RegionalOrganization{
Id: row["Id"].(int64),
Name: row["Name"].(string),
IsRegionalOrganization: row["IsRegionalOrganization"].(bool),
IsIndexRepoEnabled: row["IsIndexRepoEnabled"].(bool),
IsCopilotRequestEnabled: row["IsCopilotRequestEnabled"].(bool),
IsAccessRequestEnabled: row["IsAccessRequestEnabled"].(bool),
Expand Down Expand Up @@ -138,6 +141,7 @@ func SelectRegionalOrganizationById(id int64) (*RegionalOrganization, error) {
organization := RegionalOrganization{
Id: row["Id"].(int64),
Name: row["Name"].(string),
IsRegionalOrganization: row["IsRegionalOrganization"].(bool),
IsIndexRepoEnabled: row["IsIndexRepoEnabled"].(bool),
IsCopilotRequestEnabled: row["IsCopilotRequestEnabled"].(bool),
IsAccessRequestEnabled: row["IsAccessRequestEnabled"].(bool),
Expand Down Expand Up @@ -169,6 +173,7 @@ func UpdateRegionalOrganization(organization RegionalOrganization) error {
param := map[string]interface{}{
"Id": organization.Id,
"Name": organization.Name,
"IsRegionalOrganization": organization.IsRegionalOrganization,
"IsIndexRepoEnabled": organization.IsIndexRepoEnabled,
"IsCopilotRequestEnabled": organization.IsCopilotRequestEnabled,
"IsAccessRequestEnabled": organization.IsAccessRequestEnabled,
Expand Down Expand Up @@ -196,6 +201,7 @@ func InsertRegionalOrganization(organization RegionalOrganization) (int64, error
param := map[string]interface{}{
"Id": organization.Id,
"Name": organization.Name,
"IsRegionalOrganization": organization.IsRegionalOrganization,
"IsIndexRepoEnabled": organization.IsIndexRepoEnabled,
"IsCopilotRequestEnabled": organization.IsCopilotRequestEnabled,
"IsAccessRequestEnabled": organization.IsAccessRequestEnabled,
Expand Down
58 changes: 0 additions & 58 deletions src/goapp/pkg/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,45 +549,6 @@ func GetMembersByEnterprise(enterprise string, token string) (*GetMembersByEnter
return &result, nil
}

func GetMembersByOrganization(org string, token string) (*GetMembersByOrganizationResult, error) {
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
)
httpClient := oauth2.NewClient(context.Background(), src)
client := githubv4.NewClient(httpClient)

var query GetMembersByOrganizationQuery
var after *githubv4.String

variables := map[string]interface{}{
"login": githubv4.String(org),
"after": after,
}

var result GetMembersByOrganizationResult

for {
err := client.Query(context.Background(), &query, variables)
if err != nil {
return nil, err
}

for _, member := range query.Organization.MembersWithRole.Nodes {
result.Members = append(result.Members, Member{
Login: string(member.Login),
DatabaseId: int64(member.DatabaseId),
})
}

if !query.Organization.MembersWithRole.PageInfo.HasNextPage {
break
}
variables["after"] = githubv4.NewString(query.Organization.MembersWithRole.PageInfo.EndCursor)
}

return &result, nil
}

// Query structs
type GetOrganizationsWithinEnterpriseQuery struct {
Enterprise struct {
Expand Down Expand Up @@ -622,21 +583,6 @@ type GetMembersByEnterpriseQuery struct {
} `graphql:"enterprise(slug: $enterprise)"`
}

type GetMembersByOrganizationQuery struct {
Organization struct {
MembersWithRole struct {
Nodes []struct {
DatabaseId githubv4.Int
Login githubv4.String
}
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"membersWithRole(first: 100, after: $after)"`
} `graphql:"organization(login: $login)"`
}

type PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
Expand All @@ -651,10 +597,6 @@ type GetMembersByEnterpriseResult struct {
Members []Member
}

type GetMembersByOrganizationResult struct {
Members []Member
}

type Member struct {
Login string
DatabaseId int64
Expand Down
1 change: 1 addition & 0 deletions src/goapp/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ func setUtilityRoutes() {
httpRouter.GET("/utility/expiring-invitations", m.Chain(rtApi.ExpiringInvitation, m.GuidAuth()))
httpRouter.GET("/utility/index-ad-groups", m.Chain(rtApi.IndexADGroups, m.GuidAuth()))
httpRouter.GET("/utility/index-regional-organizations", m.Chain(rtApi.IndexRegionalOrganizations, m.GuidAuth()))
httpRouter.GET("/utility/scan-community-organizations", m.Chain(rtApi.ScanCommunityOrganizations, m.GuidAuth()))
}

func serve() {
Expand Down
105 changes: 105 additions & 0 deletions src/goapp/routes/api/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"main/pkg/appinsights_wrapper"
db "main/pkg/ghmgmtdb"
ghAPI "main/pkg/github"
"main/pkg/msgraph"
"main/pkg/notification"
"main/pkg/session"

Expand Down Expand Up @@ -471,6 +472,110 @@ func IndexRegionalOrganizations(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
}

type Member struct {
Id int64
Username string
Email string
}

func ScanCommunityOrganizations(w http.ResponseWriter, r *http.Request) {
logger := appinsights_wrapper.NewClient()
defer logger.EndOperation()

// Get all community organizations
communityOrgs := []string{os.Getenv("GH_ORG_OPENSOURCE"), os.Getenv("GH_ORG_INNERSOURCE")}
isEnabled := db.NullBool{Value: true}
regionalOrgs, err := db.SelectRegionalOrganization(&isEnabled)
if err != nil {
logger.LogException(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
for _, regionalOrg := range regionalOrgs {
if !regionalOrg.IsRegionalOrganization {
continue
}
communityOrgs = append(communityOrgs, regionalOrg.Name)
}

// Fetch all enterprise members with organizations
enterpriseToken := os.Getenv("GH_ENTERPRISE_TOKEN")
enterpriseName := os.Getenv("GH_ENTERPRISE_NAME")
enterpriseMembers, err := ghAPI.GetMembersByEnterprise(enterpriseName, enterpriseToken)
if err != nil {
logger.LogException(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Fetch all members of the Active Directory
adMembers, err := msgraph.GetAllUsers()
if err != nil {
logger.LogException(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Filter enterprise members that are in AD
var enterpriseMembersInAD []Member
for _, adMember := range adMembers {
for _, enterpriseMember := range enterpriseMembers.Members {
if enterpriseMember.EnterpriseEmail == adMember.Email {
enterpriseMembersInAD = append(enterpriseMembersInAD, Member{Id: enterpriseMember.DatabaseId, Username: enterpriseMember.Login, Email: enterpriseMember.EnterpriseEmail})
break
}
}
}

// Filter enterprise members that are in AD if they are in the community organizations
var communityMembers []Member
for _, communityOrg := range communityOrgs {
token := os.Getenv("GH_TOKEN")
// Fetch all members of the community organization
members, err := ghAPI.OrgListMembers(token, communityOrg, "all")
if err != nil {
logger.LogException(err)
continue
}

for _, member := range members {
for _, enterpriseMemberInAD := range enterpriseMembersInAD {
if member.GetLogin() == enterpriseMemberInAD.Username {
// Check if exist in communityMembers
var exist bool
for _, communityMember := range communityMembers {
if communityMember.Username == enterpriseMemberInAD.Username {
exist = true
break
}
}
if !exist {
communityMembers = append(communityMembers, Member{Id: enterpriseMemberInAD.Id, Username: enterpriseMemberInAD.Username, Email: enterpriseMemberInAD.Email})
break
}
}
}
}
}

for _, communityMember := range communityMembers {
// Check if the member associated their account with the community
email, err := db.GetUserEmailByGithubId(fmt.Sprint(communityMember.Id))
if err != nil {
logger.LogException(err)
continue
}

if email == "" {
// Notify the user to associate their account with the community
logger.LogTrace(fmt.Sprint(communityMember.Id, " ", communityMember.Username, " ", communityMember.Email), contracts.Information)
}
}

w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
}

func ReprocessCommunityApprovalRequestNewOrganizations() {
logger := appinsights_wrapper.NewClient()
defer logger.EndOperation()
Expand Down
4 changes: 4 additions & 0 deletions src/goapp/routes/api/regionalOrganization.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
type RegionalOrganizationDto struct {
Id int64 `json:"id"`
Name string `json:"name"`
IsRegionalOrganization bool `json:"isRegionalOrganization"`
IsIndexRepoEnabled bool `json:"isIndexRepoEnabled"`
IsCopilotRequestEnabled bool `json:"isCopilotRequestEnabled"`
IsAccessRequestEnabled bool `json:"isAccessRequestEnabled"`
Expand Down Expand Up @@ -119,6 +120,7 @@ func GetRegionalOrganizationByOption(w http.ResponseWriter, r *http.Request) {
regionalOrganizationDto := RegionalOrganizationDto{
Id: regionalOrganization.Id,
Name: regionalOrganization.Name,
IsRegionalOrganization: regionalOrganization.IsRegionalOrganization,
IsIndexRepoEnabled: regionalOrganization.IsIndexRepoEnabled,
IsCopilotRequestEnabled: regionalOrganization.IsCopilotRequestEnabled,
IsAccessRequestEnabled: regionalOrganization.IsAccessRequestEnabled,
Expand Down Expand Up @@ -182,6 +184,7 @@ func InsertRegionalOrganization(w http.ResponseWriter, r *http.Request) {
regionalOrganization := db.RegionalOrganization{
Id: regionalOrganizationDto.Id,
Name: regionalOrganizationDto.Name,
IsRegionalOrganization: regionalOrganizationDto.IsRegionalOrganization,
IsIndexRepoEnabled: regionalOrganizationDto.IsIndexRepoEnabled,
IsCopilotRequestEnabled: regionalOrganizationDto.IsCopilotRequestEnabled,
IsAccessRequestEnabled: regionalOrganizationDto.IsAccessRequestEnabled,
Expand Down Expand Up @@ -228,6 +231,7 @@ func UpdateRegionalOrganization(w http.ResponseWriter, r *http.Request) {
regionalOrganization := db.RegionalOrganization{
Id: id,
Name: regionalOrganizationDto.Name,
IsRegionalOrganization: regionalOrganizationDto.IsRegionalOrganization,
IsIndexRepoEnabled: regionalOrganizationDto.IsIndexRepoEnabled,
IsCopilotRequestEnabled: regionalOrganizationDto.IsCopilotRequestEnabled,
IsAccessRequestEnabled: regionalOrganizationDto.IsAccessRequestEnabled,
Expand Down
Loading

0 comments on commit 8af8833

Please sign in to comment.