Skip to content

Commit

Permalink
Adding 'add member to group' feature with Ctrl+G.
Browse files Browse the repository at this point in the history
  • Loading branch information
Macmod committed May 28, 2024
1 parent d079b7a commit 41ee705
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 26 deletions.
45 changes: 23 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,28 +134,29 @@ You can also change the address of your proxy using the `l` keybinding.
| <kbd>Ctrl</kbd> + <kbd>f</kbd> | LDAP Explorer & Object Search pages | Open the finder to search for cached objects & attributes with regex |
| Right Arrow | Explorer panel | Expand the children of the selected object |
| Left Arrow | Explorer panel | Collapse the children of the selected object |
| <kbd>r</kbd> | Explorer panel | Reload the attributes and children of the selected object |
| <kbd>Ctrl</kbd> + <kbd>n</kbd> | Explorer panel | Create a new object under the selected object |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | Explorer panel | Export all loaded nodes in the selected subtree into a JSON file |
| <kbd>Ctrl</kbd> + <kbd>p</kbd> | Explorer panel | Change the password of the selected user or computer account |
| <kbd>Ctrl</kbd> + <kbd>a</kbd> | Explorer panel | Update the userAccountControl of the object interactively |
| <kbd>Ctrl</kbd> + <kbd>l</kbd> | Explorer panel | Move the selected object to another location |
| <kbd>Delete</kbd> | Explorer panel | Delete the selected object |
| <kbd>r</kbd> | Attributes panel | Reload the attributes for the selected object |
| <kbd>Ctrl</kbd> + <kbd>e</kbd> | Attributes panel | Edit the selected attribute of the selected object |
| <kbd>Ctrl</kbd> + <kbd>n</kbd> | Attributes panel | Create a new attribute in the selected object |
| <kbd>Delete</kbd> | Attributes panel | Delete the selected attribute of the selected object |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | Object groups panel | Export the current groups into a JSON file |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | Group members panel | Export the current group members into a JSON file |
| <kbd>Ctrl</kbd> + <kbd>o</kbd> | DACL page | Change the owner of the current security descriptor |
| <kbd>Ctrl</kbd> + <kbd>k</kbd> | DACL page | Change the control flags of the current security descriptor |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | DACL page | Export the current security descriptor into a JSON file |
| <kbd>Ctrl</kbd> + <kbd>n</kbd> | DACL entries panel | Create a new ACE in the current DACL |
| <kbd>Ctrl</kbd> + <kbd>e</kbd> | DACL entries panel | Edit the selected ACE of the current DACL |
| <kbd>Delete</kbd> | DACL entries panel | Deletes the selected ACE of the current DACL |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | GPO page | Export the current GPOs and their links into a JSON file |
| <kbd>h</kbd> | Global | Show/hide headers |
| <kbd>q</kbd> | Global | Exit the program |
| <kbd>r</kbd> | Explorer panel | Reload the attributes and children of the selected object |
| <kbd>Ctrl</kbd> + <kbd>n</kbd> | Explorer panel | Create a new object under the selected object |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | Explorer panel | Export all loaded nodes in the selected subtree into a JSON file |
| <kbd>Ctrl</kbd> + <kbd>p</kbd> | Explorer panel | Change the password of the selected user or computer account |
| <kbd>Ctrl</kbd> + <kbd>a</kbd> | Explorer panel | Update the userAccountControl of the object interactively |
| <kbd>Ctrl</kbd> + <kbd>l</kbd> | Explorer panel | Move the selected object to another location |
| <kbd>Delete</kbd> | Explorer panel | Delete the selected object |
| <kbd>r</kbd> | Attributes panel | Reload the attributes for the selected object |
| <kbd>Ctrl</kbd> + <kbd>e</kbd> | Attributes panel | Edit the selected attribute of the selected object |
| <kbd>Ctrl</kbd> + <kbd>n</kbd> | Attributes panel | Create a new attribute in the selected object |
| <kbd>Delete</kbd> | Attributes panel | Delete the selected attribute of the selected object |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | Object groups panel | Export the current groups into a JSON file |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | Group members panel | Export the current group members into a JSON file |
| <kbd>Ctrl</kbd> + <kbd>g</kbd> | Groups panels / Explorer panel / Obj. Search panel | Add a member to the selected group |
| <kbd>Ctrl</kbd> + <kbd>o</kbd> | DACL page | Change the owner of the current security descriptor |
| <kbd>Ctrl</kbd> + <kbd>k</kbd> | DACL page | Change the control flags of the current security descriptor |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | DACL page | Export the current security descriptor into a JSON file |
| <kbd>Ctrl</kbd> + <kbd>n</kbd> | DACL entries panel | Create a new ACE in the current DACL |
| <kbd>Ctrl</kbd> + <kbd>e</kbd> | DACL entries panel | Edit the selected ACE of the current DACL |
| <kbd>Delete</kbd> | DACL entries panel | Deletes the selected ACE of the current DACL |
| <kbd>Ctrl</kbd> + <kbd>s</kbd> | GPO page | Export the current GPOs and their links into a JSON file |
| <kbd>h</kbd> | Global | Show/hide headers |
| <kbd>q</kbd> | Global | Exit the program |

## Tree Colors

Expand Down
7 changes: 4 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# TODO (priority)

* Feature: Support deleting specific attribute values (not the entire attribute)
* Feature: View/modify ADIDNS dnsZones and dnsNodes
* Feature: Options to manipulate (edit/create/delete) gpLinks visually
* Feature: Load initial cache from file
* Feature: Monitor object for real-time changes (DirSync/SyncRepl)

# TODO (later)

* Feature: View/modify ADIDNS dnsZones and dnsNodes
* Feature: Mini tool to convert godap exports into bloodhound dumps
* Feature: Improve object creation form (implement customizations)
* Feature: Custom themes
* Feature: Customizable keybindings
* Refactor: Improve the organization of files, functions and structures (among other ideas, remove the "utils" package)
* Wish: Mini tool to convert godap exports into bloodhound dumps
* Wish: Monitor object for real-time changes (DirSync/SyncRepl)
* Wish: Some way to copy data from panels (not implemented in tview, only for the "textarea" primitive)
* Wish: Remove dependency on personal fork of gokrb5
* Wish: Remove dependency on personal fork of go-ldap
65 changes: 65 additions & 0 deletions explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ var (
rootDNInput *tview.InputField
searchFilterInput *tview.InputField
treeFlex *tview.Flex

addMemberToGroupFormValidation bool
)

func initExplorerPage() {
Expand Down Expand Up @@ -367,6 +369,67 @@ func openCreateObjectForm(node *tview.TreeNode, done func()) {
app.SetRoot(createObjectForm, true).SetFocus(createObjectForm)
}

func openAddMemberToGroupForm(groupDN string) {
currentFocus := app.GetFocus()

addMemberForm := NewXForm().
AddInputField("Group DN", groupDN, 0, nil, nil).
AddInputField("Object Name", "", 0, nil, nil).
AddTextView("Object DN", "", 0, 1, false, true)

objectNameFormItem := addMemberForm.GetFormItemByLabel("Object Name").(*tview.InputField)
objectNameFormItem.
SetPlaceholderStyle(placeholderStyle).
SetPlaceholderTextColor(placeholderTextColor).
SetFieldBackgroundColor(fieldBackgroundColor).
SetPlaceholder("sAMAccountName or DN")

objectDNFormItem := addMemberForm.GetFormItemByLabel("Object DN").(*tview.TextView)
objectDNFormItem.SetDynamicColors(true)

objectNameFormItem.SetDoneFunc(func(key tcell.Key) {
objectDN, err := lc.FindFirstDN(objectNameFormItem.GetText())
if err == nil {
addMemberToGroupFormValidation = true
objectDNFormItem.SetText(objectDN)
} else {
addMemberToGroupFormValidation = false
objectDNFormItem.SetText("[red]Object not found")
}
})

addMemberForm.
SetButtonBackgroundColor(formButtonBackgroundColor).
SetButtonTextColor(formButtonTextColor).
SetButtonActivatedStyle(formButtonActivatedStyle).
SetInputCapture(handleEscapeToTree)

addMemberForm.
AddButton("Go Back", func() {
app.SetRoot(appPanel, true).SetFocus(currentFocus)
}).
AddButton("Add", func() {
if !addMemberToGroupFormValidation {
// TODO: Provide some feedback to the user?
return
}

objectDN := addMemberForm.GetFormItemByLabel("Object DN").(*tview.TextView).GetText(true)

err = lc.AddMemberToGroup(objectDN, groupDN)
if err != nil {
updateLog(fmt.Sprintf("%s", err), "red")
} else {
updateLog("Member '"+objectDN+"' added to group '"+groupDN+"'", "green")
}

app.SetRoot(appPanel, true).SetFocus(currentFocus)
})

addMemberForm.SetTitle("Add Group Member").SetBorder(true)
app.SetRoot(addMemberForm, true).SetFocus(addMemberForm)
}

func treePanelKeyHandler(event *tcell.EventKey) *tcell.EventKey {
currentNode := treePanel.GetCurrentNode()
if currentNode == nil {
Expand Down Expand Up @@ -460,6 +523,8 @@ func treePanelKeyHandler(event *tcell.EventKey) *tcell.EventKey {
reloadExplorerPage()
}
})
case tcell.KeyCtrlG:
openAddMemberToGroupForm(baseDN)
}

return event
Expand Down
18 changes: 18 additions & 0 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,20 +253,38 @@ func exportCurrentMembers() {
}

func groupsKeyHandler(event *tcell.EventKey) *tcell.EventKey {
row, col := groupsPanel.GetSelection()

switch event.Key() {
case tcell.KeyCtrlS:
exportCurrentGroups()
return nil
case tcell.KeyCtrlG:
selCell := groupsPanel.GetCell(row, col)
if selCell != nil && selCell.GetReference() != nil {
baseDN := selCell.GetReference().(string)
openAddMemberToGroupForm(baseDN)
}
return nil
}

return event
}

func membersKeyHandler(event *tcell.EventKey) *tcell.EventKey {
row, col := membersPanel.GetSelection()

switch event.Key() {
case tcell.KeyCtrlS:
exportCurrentMembers()
return nil
case tcell.KeyCtrlG:
selCell := membersPanel.GetCell(row, col)
if selCell != nil && selCell.GetReference() != nil {
baseDN := selCell.GetReference().(string)
openAddMemberToGroupForm(baseDN)
}
return nil
}

return event
Expand Down
1 change: 1 addition & 0 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func initHelpPage() {
{"Delete", "Attributes panel", "Delete the selected attribute of the selected object"},
{"Ctrl + s", "Object groups panel", "Export the current groups innto a JSON file"},
{"Ctrl + s", "Group members panel", "Export the current group members into a JSON file"},
{"Ctrl + g", "Groups panels / Explorer panel / Obj. Search panel", "Add a member to the selected group"},
{"Ctrl + o", "DACL page", "Change the owner of the current security descriptor"},
{"Ctrl + k", "DACL page", "Change the control flags of the current security descriptor"},
{"Ctrl + s", "DACL page", "Export the current security descriptor into a JSON file"},
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"h12.io/socks"
)

var godapVer = "Godap v2.5.0"
var godapVer = "Godap v2.6.0"

var (
ldapServer string
Expand Down
5 changes: 5 additions & 0 deletions search.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ func initSearchPage() {
if currentNode.GetReference() != nil {
openCreateObjectForm(currentNode, nil)
}
case tcell.KeyCtrlG:
if currentNode.GetReference() != nil {
baseDN := currentNode.GetReference().(string)
openAddMemberToGroupForm(baseDN)
}
}

return event
Expand Down
26 changes: 26 additions & 0 deletions utils/ldapactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ func (lc *LDAPConn) QueryGroupMembers(groupDN string) (group []*ldap.Entry, err
return result.Entries, nil
}

func (lc *LDAPConn) AddMemberToGroup(memberDN string, groupDN string) error {
modifyRequest := ldap.NewModifyRequest(groupDN, nil)
modifyRequest.Add("member", []string{memberDN})
err := lc.Conn.Modify(modifyRequest)
if err != nil {
return err
}

return nil
}

func (lc *LDAPConn) QueryUserGroups(userName string) ([]*ldap.Entry, error) {
samOrDn, _ := SamOrDN(userName)

Expand All @@ -266,6 +277,21 @@ func (lc *LDAPConn) QueryUserGroups(userName string) ([]*ldap.Entry, error) {
return result.Entries, nil
}

func (lc *LDAPConn) FindFirstDN(identifier string) (string, error) {
samOrDn, _ := SamOrDN(identifier)

entries, err := lc.Query(lc.RootDN, samOrDn, ldap.ScopeWholeSubtree, false)
if err != nil {
return "", err
}

if len(entries) > 0 {
return entries[0].DN, nil
} else {
return "", fmt.Errorf("Object not found")
}
}

// User Passwords
// Reference: https://gist.github.com/Project0/61c13130563cf7f595e031d54fe55aab
const (
Expand Down

0 comments on commit 41ee705

Please sign in to comment.