1
0
mirror of https://github.com/altlinux/admc.git synced 2025-03-20 02:50:07 +03:00

switch to qt

This commit is contained in:
Dmitry Degtyarev 2020-05-12 17:26:41 +04:00
parent 33e6b5ad4c
commit 192cd370d6
36 changed files with 1018 additions and 3347 deletions

5
.gitignore vendored
View File

@ -1 +1,6 @@
obj/*
moc/*
Makefile
adtool
.qmake.stash
*.pro.user

View File

@ -1,31 +0,0 @@
EXE := adtool
SRCDIR := src
OBJDIR := obj
SOURCES += $(shell find $(SRCDIR)/ -name '*.c')
OBJS := $(addprefix $(OBJDIR)/, $(addsuffix .o, $(basename $(notdir $(SOURCES)))))
CFLAGS += -Isrc/libs/
CFLAGS += -g -Wall -Wformat
CFLAGS += `pkg-config --cflags gtk+-3.0`
CFLAGS += -rdynamic
LIBS += `pkg-config --libs gtk+-3.0`
LIBS += -lldap -llber -lresolv -lgsasl
$(shell mkdir -p $(OBJDIR))
$(OBJDIR)/%.o: $(SRCDIR)/%.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/%.o: $(SRCDIR)/libs/%.c
$(CC) $(CFLAGS) -c -o $@ $<
all: $(EXE)
@echo Build complete
$(EXE): $(OBJS)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
clean:
rm -f $(EXE) $(OBJS)

View File

@ -7,5 +7,5 @@ Requirements:
* `libkrb5-devel`
* `libgsasl-devel`
Run as `./adtool fake` to load fake entries and test without LDAP connection.

20
adtool.pro Normal file
View File

@ -0,0 +1,20 @@
QT += core gui widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
LIBS += -lldap -llber -lresolv -lgsasl
SOURCES = src/*.cpp
HEADERS = src/*.h
SOURCES += src/active_directory.c
DESTDIR = .
OBJECTS_DIR = obj
UI_DIR = src/ui
MOC_DIR = moc
FORMS += \
data/ui/mainwindow.ui

View File

@ -1,490 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<object class="GtkTreeStore" id="attributes_model">
<columns>
<!-- column-name Name -->
<column type="gchararray"/>
<!-- column-name Value -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkTreeStore" id="containers_model">
<columns>
<!-- column-name containers_model_column_DN -->
<column type="gchararray"/>
<!-- column-name containers_model_column_Name -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="containers_model_filter">
<property name="child_model">containers_model</property>
</object>
<object class="GtkListStore" id="contents_model">
<columns>
<!-- column-name DN -->
<column type="gchararray"/>
<!-- column-name Name -->
<column type="gchararray"/>
<!-- column-name Type -->
<column type="gchararray"/>
<!-- column-name Description -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="contents_model_filter">
<property name="child_model">contents_model</property>
</object>
<object class="GtkWindow" id="window">
<property name="width_request">900</property>
<property name="height_request">600</property>
<property name="can_focus">False</property>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuBar" id="menu_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="menu_bar_file">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_File</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="menu_bar_file_new">
<property name="label">gtk-new</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="menu_bar_file_new_user">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">User</property>
<property name="use_underline">True</property>
<signal name="activate" handler="menu_bar_file_new_user_func" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-save-as</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-quit</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_bar_edit">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Edit</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-cut</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_bar_view">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_View</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkCheckMenuItem" id="advanced_view_toggle">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Advanced View</property>
<property name="use_underline">True</property>
<signal name="toggled" handler="advanced_view_toggled_func" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_bar_help">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Help</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-about</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkPaned" id="outer_paned">
<property name="width_request">200</property>
<property name="height_request">500</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkTreeView" id="containers_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">containers_model_filter</property>
<signal name="row-collapsed" handler="containers_row_collapsed_func" swapped="no"/>
<signal name="row-expanded" handler="containers_row_expanded_func" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
<signal name="changed" handler="containers_selection_changed_func" swapped="no"/>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Name</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkPaned" id="inner_paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkTreeView" id="contents_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">contents_model_filter</property>
<child internal-child="selection">
<object class="GtkTreeSelection">
<signal name="changed" handler="contents_selection_changed_func" swapped="no"/>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Name</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Category</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Description</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">DN</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkTreeView" id="attributes_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">attributes_model</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Name</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Value</property>
<child>
<object class="GtkCellRendererText">
<property name="editable">True</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
<object class="GtkDialog" id="new_user_dialog">
<property name="can_focus">False</property>
<property name="type_hint">dialog</property>
<child type="titlebar">
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button1">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button2">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="new_user_dialog_name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">name</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-5">button1</action-widget>
<action-widget response="-6">button2</action-widget>
</action-widgets>
</object>
</interface>

161
data/ui/mainwindow.ui Normal file
View File

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1307</width>
<height>637</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QSplitter" name="splitter">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1301</width>
<height>591</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="ContainersView" name="containers_view">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="rootIsDecorated">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>true</bool>
</attribute>
</widget>
<widget class="ContentsView" name="contents_view">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>true</bool>
</attribute>
</widget>
<widget class="AttributesView" name="attributes_view">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1307</width>
<height>27</height>
</rect>
</property>
<widget class="QMenu" name="menuhello">
<property name="title">
<string>New</string>
</property>
<addaction name="actionWhat"/>
<addaction name="actionSecond"/>
</widget>
<widget class="QMenu" name="menuEdit">
<property name="title">
<string>Edit</string>
</property>
<addaction name="actionSomething"/>
<addaction name="actionHere"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
<addaction name="menubar_view_advancedView"/>
</widget>
<addaction name="menuhello"/>
<addaction name="menuEdit"/>
<addaction name="menuView"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionWhat">
<property name="text">
<string>What</string>
</property>
</action>
<action name="actionSecond">
<property name="text">
<string>Second</string>
</property>
</action>
<action name="actionSomething">
<property name="text">
<string>Something</string>
</property>
</action>
<action name="actionHere">
<property name="text">
<string>Here</string>
</property>
</action>
<action name="menubar_view_advancedView">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Advanced view</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>ContainersView</class>
<extends>QTreeView</extends>
<header>src/containers_view.h</header>
</customwidget>
<customwidget>
<class>ContentsView</class>
<extends>QTreeView</extends>
<header>src/contents_view.h</header>
</customwidget>
<customwidget>
<class>AttributesView</class>
<extends>QTreeView</extends>
<header>src/attributes_view.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

37
src/ad_filter.cpp Normal file
View File

@ -0,0 +1,37 @@
#include "ad_filter.h"
#include "ad_model.h"
#include <QAction>
AdFilter::AdFilter(const QAction * const advanced_view_action, bool only_show_containers) {
this->only_show_containers = only_show_containers;
// On advanced view toggle, copy advanced view flag and invalidate filter
connect(advanced_view_action, &QAction::toggled,
[this](bool checked)
{
this->advanced_view = checked;
this->invalidateFilter();
});
}
bool AdFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
auto index = sourceModel()->index(source_row, 0, source_parent);
// Hide advanced view only entries if advanced view is OFF
auto advanced_view_only = index.data(AdModel::Roles::AdvancedViewOnly).toBool();
if (advanced_view_only && !this->advanced_view) {
return false;
}
if (only_show_containers) {
// Hide non-containers
auto is_container = index.data(AdModel::Roles::IsContainer).toBool();
if (!is_container) {
return false;
}
}
return true;
}

22
src/ad_filter.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <QSortFilterProxyModel>
class QModelIndex;
class QAction;
class AdModel;
// TODO: only allow AdModel source models
// Filter out advanced entries when advanced view is off
// Connected to advanced view toggle in menubar
class AdFilter : public QSortFilterProxyModel {
public:
explicit AdFilter(const QAction * const advanced_view_action, bool only_show_containers = false);
private:
bool advanced_view = false;
bool only_show_containers;
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
};

183
src/ad_interface.cpp Normal file
View File

@ -0,0 +1,183 @@
#include "ad_interface.h"
#include "constants.h"
extern "C" {
#include "active_directory.h"
}
// TODO: replace C active_directory.h with C++ version
// -----------------------------------------------------------------
// FAKE STUFF
// -----------------------------------------------------------------
bool FAKE_AD = false;
QMap<QString, QList<QString>> fake_children;
QMap<QString, QMap<QString, QList<QString>>> fake_attributes;
void fake_ad_init() {
fake_children[HEAD_DN] = {
QString("CN=A,") + HEAD_DN,
QString("CN=B,") + HEAD_DN,
QString("CN=C,") + HEAD_DN,
QString("CN=D,") + HEAD_DN,
};
fake_attributes[HEAD_DN] = {
{"name", {"domain"}},
{"objectClass", {"container"}},
{"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
{"showInAdvancedViewOnly", {"FALSE"}},
};
fake_attributes[QString("CN=A,") + HEAD_DN] = {
{"name", {"A"}},
{"objectClass", {"container"}},
{"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
{"showInAdvancedViewOnly", {"FALSE"}},
{"description", {"A's description"}},
};
fake_attributes[QString("CN=B,") + HEAD_DN] = {
{"name", {"B"}},
{"objectClass", {"container"}},
{"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
{"showInAdvancedViewOnly", {"FALSE"}},
{"description", {"B's description"}},
};
fake_attributes[QString("CN=C,") + HEAD_DN] = {
{"name", {"C"}},
{"objectClass", {"container"}},
{"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
{"showInAdvancedViewOnly", {"FALSE"}},
};
fake_attributes[QString("CN=D,") + HEAD_DN] = {
{"name", {"D"}},
{"objectClass", {"container"}},
{"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
{"showInAdvancedViewOnly", {"TRUE"}},
};
fake_children[QString("CN=A,") + HEAD_DN] = {
QString("CN=B's child,CN=B,") + HEAD_DN
};
fake_attributes[QString("CN=B's child,CN=B,") + HEAD_DN] = {
{"name", {"B's child"}},
{"objectClass", {"person"}},
{"objectCategory", {"CN=Person,CN=Schema,CN=Configuration"}},
{"showInAdvancedViewOnly", {"FALSE"}},
};
}
QList<QString> fake_load_children(QString &dn) {
if (!fake_children.contains(dn)) {
// NOTE: ok to have empty children for leaves
fake_children[dn] = QList<QString>();
}
return fake_children[dn];
}
QMap<QString, QList<QString>> fake_load_attributes(QString &dn) {
if (!fake_attributes.contains(dn)) {
printf("load_attributes failed for %s, loading empty attributes\n", qPrintable(dn));
fake_attributes[dn] = QMap<QString, QList<QString>>();
}
return fake_attributes[dn];
}
// -----------------------------------------------------------------
// REAL STUFF
// -----------------------------------------------------------------
bool ad_interface_login() {
if (FAKE_AD) {
fake_ad_init();
return true;
}
LDAP* ldap_connection = ad_login();
if (ldap_connection == NULL) {
printf("ad_login error: %s\n", ad_get_error());
return false;
} else {
return true;
}
}
QList<QString> load_children(QString &dn) {
if (FAKE_AD) {
return fake_load_children(dn);
}
char **children_raw = ad_list(dn.toLatin1().data());
// TODO: error check
if (children_raw != NULL) {
auto children = QList<QString>();
for (int i = 0; children_raw[i] != NULL; i++) {
auto child = QString(children_raw[i]);
children.push_back(child);
}
for (int i = 0; children_raw[i] != NULL; i++) {
free(children_raw[i]);
}
free(children_raw);
return children;
} else {
return QList<QString>();
}
}
QMap<QString, QList<QString>> load_attributes(QString &dn) {
if (FAKE_AD) {
return fake_load_attributes(dn);
}
// TODO: save original attributes ordering and load it like that into model
char** attributes_raw = ad_get_attribute(dn.toLatin1().data(), NULL);
// TODO: error check
if (attributes_raw != NULL) {
auto attributes = QMap<QString, QList<QString>>();
// Load attributes map
// attributes_raw is in the form of:
// char** array of {key, value, value, key, value ...}
// transform it into:
// map of {key => {value, value ...}, key => {value, value ...} ...}
for (int i = 0; attributes_raw[i + 2] != NULL; i += 2) {
auto attribute = QString(attributes_raw[i]);
auto value = QString(attributes_raw[i + 1]);
// Make values list if doesn't exist yet
if (!attributes.contains(attribute)) {
attributes[attribute] = QList<QString>();
}
attributes[attribute].push_back(value);
}
// Free attributes_raw
for (int i = 0; attributes_raw[i] != NULL; i++) {
free(attributes_raw[i]);
}
free(attributes_raw);
return attributes;
} else {
return QMap<QString, QList<QString>>();
}
}

15
src/ad_interface.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <QList>
#include <QString>
#include <QMap>
// Interface functions to convert from raw char** active directory returns to Qt containers
// Also can load fake data if program is run with "fake" option
extern bool FAKE_AD;
bool ad_interface_login();
QList<QString> load_children(QString &dn);
QMap<QString, QList<QString>> load_attributes(QString &dn);

134
src/ad_model.cpp Normal file
View File

@ -0,0 +1,134 @@
#include "ad_model.h"
#include "ad_interface.h"
#include "constants.h"
#include <QMap>
QList<QStandardItem*> make_row(QString dn) {
auto attributes = load_attributes(dn);
// TODO: get rid of "if (x.contains(y))"
// Name
auto name = dn;
if (attributes.contains("name")) {
name = attributes["name"][0];
}
// Category
auto category = dn;
if (attributes.contains("objectCategory")) {
// NOTE: raw category is given as DN
// TODO: convert it completely (turn '-' into ' ')
auto category_as_dn = attributes["objectCategory"][0];
auto equals_index = category_as_dn.indexOf('=');
auto comma_index = category_as_dn.indexOf(',');
auto segment_length = comma_index - equals_index;
// TODO: check what happens if equals is negative
category = category_as_dn.mid(equals_index + 1, segment_length);
}
// Description
QString description = "none";
if (attributes.contains("description")) {
description = attributes["description"][0];
}
// showInAdvancedViewOnly
bool advanced_view = false;
if (attributes.contains("showInAdvancedViewOnly")) {
auto advanced_view_str = attributes["showInAdvancedViewOnly"][0];
if (advanced_view_str == "TRUE") {
advanced_view = true;
}
}
// is container
bool is_container = false;
if (attributes.contains("objectClass")) {
auto objectClasses = attributes["objectClass"];
QList<QString> container_objectClasses = {"container", "organizationalUnit", "builtinDomain", "domain"};
for (auto e : container_objectClasses) {
if (objectClasses.contains(e)) {
is_container = true;
break;
}
}
}
QList<QStandardItem*> row = {
new QStandardItem(name),
new QStandardItem(category),
new QStandardItem(description),
};
// NOTE: store special invisible attributes in Roles of first item
// TODO: shouldn't store these in roles
row[0]->setData(dn, AdModel::Roles::DN);
row[0]->setData(advanced_view, AdModel::Roles::AdvancedViewOnly);
row[0]->setData(true, AdModel::Roles::CanFetch);
row[0]->setData(is_container, AdModel::Roles::IsContainer);
return row;
}
AdModel::AdModel(): QStandardItemModel(0, Column::COUNT) {
this->setHorizontalHeaderItem(Column::Name, new QStandardItem("Name"));
this->setHorizontalHeaderItem(Column::Category, new QStandardItem("Category"));
this->setHorizontalHeaderItem(Column::Description, new QStandardItem("Description"));
// Load head
auto invis_root = this->invisibleRootItem();
auto head_row = make_row(HEAD_DN);
invis_root->appendRow(head_row);
}
bool AdModel::canFetchMore(const QModelIndex &parent) const {
if (!parent.isValid()) {
return false;
}
auto dn = parent.data(Roles::DN).toString();
// printf("canFetchMore: %s\n", qPrintable(dn));
auto can_fetch = parent.data(AdModel::Roles::CanFetch).toBool();
return can_fetch;
}
void AdModel::fetchMore(const QModelIndex &parent) {
if (!parent.isValid() || !this->canFetchMore(parent)) {
return;
}
auto dn = parent.data(Roles::DN).toString();
auto parent_item = this->itemFromIndex(parent);
// Add children
auto children = load_children(dn);
for (auto child : children) {
auto child_row = make_row(child);
parent_item->appendRow(child_row);
}
// Unset CanFetch flag
parent_item->setData(false, AdModel::Roles::CanFetch);
// printf("fetchMore: %s\n", qPrintable(dn));
}
// Override this so that unexpanded and unfetched items show the expander even though they technically don't have any children loaded
// NOTE: expander is show if hasChildren returns true
bool AdModel::hasChildren(const QModelIndex &parent = QModelIndex()) const {
if (this->canFetchMore(parent)) {
return true;
} else {
return QStandardItemModel::hasChildren(parent);
}
}

31
src/ad_model.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include <QStandardItemModel>
class AdModel: public QStandardItemModel {
Q_OBJECT
public:
enum Column {
Name,
Category,
Description,
COUNT,
};
explicit AdModel();
enum Roles {
DN = Qt::UserRole + 1,
AdvancedViewOnly = Qt::UserRole + 2,
CanFetch = Qt::UserRole + 3,
IsContainer = Qt::UserRole + 4,
};
bool canFetchMore(const QModelIndex &parent) const;
void fetchMore(const QModelIndex &parent);
bool hasChildren(const QModelIndex &parent) const override;
private:
};

18
src/attributes_model.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "attributes_model.h"
AttributesModel::AttributesModel(): QStandardItemModel(0, Column::COUNT) {
this->setHorizontalHeaderItem(Column::Name, new QStandardItem("Name"));
this->setHorizontalHeaderItem(Column::Value, new QStandardItem("Value"));
}
// This will be called when an attribute value is edited
bool AttributesModel::setData(const QModelIndex &index, const QVariant &value, int role) {
QStandardItemModel::setData(index, value, role);
// TODO: attribute edit can fail for many reasons, handle it
// TODO: reload attributes in ad model
return true;
}

21
src/attributes_model.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <QStandardItemModel>
class AttributesModel: public QStandardItemModel {
Q_OBJECT
public:
enum Column {
Name,
Value,
COUNT,
};
explicit AttributesModel();
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
private:
};

View File

@ -1,99 +0,0 @@
#include "attributes_view.h"
#include "constants.h"
#include "utils.h"
#include "entry.h"
#include "stb_ds.h"
#include <gtk/gtk.h>
// Lists attributes of target entry
// Attributes target entry is selected in contents view
enum {
ATTRIBUTES_COLUMN_NAME,
ATTRIBUTES_COLUMN_VALUE,
ATTRIBUTES_COLUMN_COUNT
};
GtkTreeView* attributes_view = NULL;
char* attributes_target = NULL;
// NOTE: currently doesn't work for multi-valued attributes, add that capability when/if it's needed
void attributes_value_edited_func(
GtkCellRendererText* cell,
gchar* path_string,
gchar* new_text,
gpointer user_data)
{
GtkTreeModel* model = gtk_tree_view_get_model(attributes_view);
GtkTreeIter iter;
gtk_tree_model_get_iter_from_string(model, &iter, path_string);
char* attribute_name;
gtk_tree_model_get(model, &iter, ATTRIBUTES_COLUMN_NAME, &attribute_name, -1);
char* old_value;
gtk_tree_model_get(model, &iter, ATTRIBUTES_COLUMN_VALUE, &old_value, -1);
entry* e = get_entry(attributes_target);
bool edit_success = entry_edit_value(e, attribute_name, new_text);
if (edit_success) {
gtk_tree_store_set(GTK_TREE_STORE(model), &iter, ATTRIBUTES_COLUMN_VALUE, new_text, -1);
printf("attribute %s changed: %s > %s\n", attribute_name, old_value, new_text);
} else {
// TODO: display error
}
free(attribute_name);
free(old_value);
}
void attributes_change_target(const char* new_target_dn) {
if (attributes_target != NULL) {
free(attributes_target);
}
attributes_target = strdup(new_target_dn);
}
// NOTE: model is set when an entry is selected in contents pane
void attributes_populate_model() {
// Populate model
// List all key->value pairs in order
GtkTreeStore* model = GTK_TREE_STORE(gtk_tree_view_get_model(attributes_view));
gtk_tree_store_clear(model);
entry* e = get_entry(attributes_target);
// Target is invalid
// NOTE: this is valid behavior and can occur when target entry is deleted for example
if (e == NULL) {
return;
}
STR_ARRAY attribute_keys = e->attribute_keys;
for (int i = 0; i < arrlen(attribute_keys); i++) {
char* key = attribute_keys[i];
STR_ARRAY values = shget(e->attributes, key);
for (int j = 0; j < arrlen(values); j++) {
GtkTreeIter this_node;
gtk_tree_store_append(model, &this_node, NULL);
gtk_tree_store_set(model, &this_node, ATTRIBUTES_COLUMN_NAME, key, -1);
gtk_tree_store_set(model, &this_node, ATTRIBUTES_COLUMN_VALUE, values[j], -1);
// TODO: handle this, can't draw this text properly
if (streql(key, "objectGUID")) {
gtk_tree_store_set(model, &this_node, ATTRIBUTES_COLUMN_VALUE, "!!!can't render this for now", -1);
}
}
}
}
void attributes_init(GtkBuilder* builder) {
attributes_view = GTK_TREE_VIEW(gtk_builder_get_object_CHECKED(builder, "attributes_view"));
}

42
src/attributes_view.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "attributes_view.h"
#include "ad_model.h"
#include "ad_interface.h"
// TODO: on attribute edit, update entry in ad model
// since attributes affect model's contents
void AttributesView::set_target_from_selection(const QItemSelection &selected, const QItemSelection &) {
// Clear model of previous root
// TODO: get rid of cast
auto model = qobject_cast<QStandardItemModel *>(this->model());
if (model != nullptr) {
model->clear();
model->setHorizontalHeaderItem(Column::Name, new QStandardItem("Name"));
model->setHorizontalHeaderItem(Column::Value, new QStandardItem("Value"));
}
// Convert selection to dn
auto indexes = selected.indexes();
if (indexes.size() == 0) {
return;
}
auto index = indexes[0];
this->root_dn = index.data(AdModel::Roles::DN).toString();
// Populate model with attributes of new root
auto attributes = load_attributes(this->root_dn);
for (auto attribute : attributes.keys()) {
auto values = attributes[attribute];
for (auto value : values) {
auto name_item = new QStandardItem(attribute);
auto value_item = new QStandardItem(value);
name_item->setEditable(false);
model->appendRow({name_item, value_item});
}
}
}

View File

@ -1,7 +1,25 @@
#pragma once
#include <gtk/gtk.h>
#include <QTreeView>
void attributes_init(GtkBuilder* builder);
void attributes_change_target(const char* new_target_dn);
void attributes_populate_model();
// Shows names and values of attributes of the entry selected in contents view
class AttributesView : public QTreeView {
Q_OBJECT
public:
using QTreeView::QTreeView;
public slots:
void set_target_from_selection(const QItemSelection &selected, const QItemSelection &deselected);
private:
enum Column {
Name,
Value,
COUNT,
};
QString root_dn;
};

View File

@ -1,4 +1,4 @@
#pragma once
#define DN_LENGTH_MAX 1000
#define HEAD_DN "DC=domain,DC=alt"
#define HEAD_DN "DC=domain,DC=alt"

View File

@ -1,143 +0,0 @@
#include "containers_view.h"
#include "contents_view.h"
#include "constants.h"
#include "utils.h"
#include "entry.h"
#include "menu_bar.h"
#include "stb_ds.h"
#include <string.h>
#include <stdbool.h>
#include <gtk/gtk.h>
// Shows a tree of LDAP containers
enum {
CONTAINERS_COLUMN_DN,
CONTAINERS_COLUMN_NAME,
};
GtkTreeView* containers_view = NULL;
void containers_refilter() {
GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(containers_view));
gtk_tree_model_filter_refilter(model_filter);
}
void containers_row_expanded_func(
GtkTreeView *tree_view,
GtkTreeIter *iter,
GtkTreePath *path,
gpointer user_data)
{
GtkTreeModel* model = gtk_tree_view_get_model(tree_view);
char* dn;
gtk_tree_model_get(model, iter, CONTAINERS_COLUMN_DN, &dn, -1);
printf("containers_row_expanded %s\n", dn);
free(dn);
}
void containers_row_collapsed_func(
GtkTreeView *tree_view,
GtkTreeIter *iter,
GtkTreePath *path,
gpointer user_data)
{
// printf("containers_row_expanded %s\n", dn);
}
void containers_selection_changed_func(GtkTreeSelection* selection, gpointer user_data) {
// Get selected iter
GtkTreeView* view = gtk_tree_selection_get_tree_view(selection);
GtkTreeModel* model = gtk_tree_view_get_model(view);
GtkTreeIter iter;
gboolean any_selected = gtk_tree_selection_get_selected(selection, &model, &iter);
if (!any_selected) {
return;
}
// Get dn of selected iter
char* dn;
gtk_tree_model_get(model, &iter, CONTAINERS_COLUMN_DN, &dn, -1);
// Update contents model
contents_change_target(dn);
contents_populate_model();
free(dn);
}
gboolean containers_filter_func(
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
// Filter out entries with "showInAdvancedViewOnly" set to TRUE
gboolean visible = TRUE;
char* dn;
gtk_tree_model_get(model, iter, CONTAINERS_COLUMN_DN, &dn, -1);
if (dn != NULL) {
entry* e = get_entry(dn);
char* showInAdvancedViewOnly = entry_get_attribute_or_none(e, "showInAdvancedViewOnly");
if (!advanced_view_is_on() && streql(showInAdvancedViewOnly, "TRUE")) {
visible = FALSE;
}
g_free(dn);
}
return visible;
}
void containers_populate_model_recursive(GtkTreeStore* model, char* node_dn, GtkTreeIter* parent) {
// Populate model with name's of entries
entry* e = get_entry(node_dn);
// Skip if entry is not a container
if (!entry_is_container(e)) {
return;
}
GtkTreeIter this_node;
gtk_tree_store_append(model, &this_node, parent);
gtk_tree_store_set(model, &this_node, CONTAINERS_COLUMN_DN, e->dn, -1);
char name[DN_LENGTH_MAX];
first_element_in_dn(name, e->dn, DN_LENGTH_MAX);
gtk_tree_store_set(model, &this_node, CONTAINERS_COLUMN_NAME, name, -1);
// Recurse into entry's children
for (int i = 0; i < arrlen(e->children); i++) {
char* child_dn = e->children[i];
containers_populate_model_recursive(model, child_dn, &this_node);
}
}
void containers_populate_model() {
GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(containers_view));
GtkTreeStore* model = GTK_TREE_STORE(gtk_tree_model_filter_get_model(model_filter));
gtk_tree_store_clear(model);
containers_populate_model_recursive(model, HEAD_DN, NULL);
gtk_tree_model_filter_refilter(model_filter);
}
void containers_init(GtkBuilder* builder) {
containers_view = GTK_TREE_VIEW(gtk_builder_get_object_CHECKED(builder, "containers_view"));
// Set filter func
GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER((gtk_tree_view_get_model(containers_view)));
gtk_tree_model_filter_set_visible_func(model_filter, containers_filter_func, NULL, NULL);
containers_populate_model();
}

10
src/containers_view.cpp Normal file
View File

@ -0,0 +1,10 @@
#include "containers_view.h"
#include "ad_model.h"
#include <QSortFilterProxyModel>
#include <QItemSelection>
ContainersView::ContainersView(QWidget *parent) : QTreeView(parent) {
};

View File

@ -1,7 +1,19 @@
#pragma once
#include <gtk/gtk.h>
#include <QTreeView>
void containers_init(GtkBuilder* builder);
void containers_refilter();
void containers_populate_model();
// Shows names of AdModel as a tree
class ContainersView : public QTreeView {
Q_OBJECT
public:
explicit ContainersView(QWidget *parent = nullptr);
public slots:
signals:
private:
};

View File

@ -1,135 +0,0 @@
#include "contents_view.h"
#include "attributes_view.h"
#include "entry.h"
#include "constants.h"
#include "menu_bar.h"
#include "utils.h"
#include "stb_ds.h"
#include <gtk/gtk.h>
// Shows a list of LDAP entries that children of current target entry
// Contents target entry is selected in containers view
enum {
CONTENTS_COLUMN_DN,
CONTENTS_COLUMN_NAME,
CONTENTS_COLUMN_CATEGORY,
CONTENTS_COLUMN_DESCRIPTION,
CONTENTS_COLUMN_COUNT
};
GtkTreeView* contents_view = NULL;
char* contents_target = NULL;
void contents_refilter() {
GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(contents_view));
gtk_tree_model_filter_refilter(model_filter);
}
gboolean contents_filter_func(
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
// Filter out entries with "showInAdvancedViewOnly" set to TRUE
gboolean visible = TRUE;
char* dn;
gtk_tree_model_get(model, iter, CONTENTS_COLUMN_DN, &dn, -1);
if (dn != NULL) {
entry* e = get_entry(dn);
STR_ARRAY showInAdvancedViewOnly = entry_get_attribute(e, "showInAdvancedViewOnly");
if (!advanced_view_is_on() && showInAdvancedViewOnly != NULL && streql(showInAdvancedViewOnly[0], "TRUE")) {
visible = FALSE;
}
g_free(dn);
}
return visible;
}
void contents_selection_changed_func(GtkTreeSelection* selection, gpointer user_data) {
GtkTreeView* view = gtk_tree_selection_get_tree_view(selection);
GtkTreeModel* model = gtk_tree_view_get_model(view);
GtkTreeIter iter;
gboolean any_selected = gtk_tree_selection_get_selected(selection, &model, &iter);
if (!any_selected) {
return;
}
// Get dn of selected iter
char* dn;
gtk_tree_model_get(model, &iter, CONTENTS_COLUMN_DN, &dn, -1);
// Update contents model
attributes_change_target(dn);
attributes_populate_model();
free(dn);
}
void contents_change_target(const char* new_target_dn) {
if (contents_target != NULL) {
free(contents_target);
}
contents_target = strdup(new_target_dn);
}
// NOTE: contents model is repopulated everytime a new container is selected
void contents_populate_model() {
GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(contents_view));
GtkListStore* model = GTK_LIST_STORE(gtk_tree_model_filter_get_model(model_filter));
gtk_list_store_clear(model);
if (contents_target == NULL) {
printf("ERROR contents_populate_model(): no target\n");
return;
}
entry* e = get_entry(contents_target);
// Target is invalid
// NOTE: this is valid behavior and can occur when target entry is deleted for example
if (e == NULL) {
return;
}
// Populate model
for (int i = 0; i < arrlen(e->children); i++) {
char* child_dn = e->children[i];
entry* child = get_entry(child_dn);
GtkTreeIter this_node;
gtk_list_store_append(model, &this_node);
char* dn = child->dn;
gtk_list_store_set(model, &this_node, CONTENTS_COLUMN_DN, dn, -1);
char name[DN_LENGTH_MAX];
first_element_in_dn(name, dn, DN_LENGTH_MAX);
gtk_list_store_set(model, &this_node, CONTENTS_COLUMN_NAME, name, -1);
char* category_dn = entry_get_attribute_or_none(child, "objectCategory");
gtk_list_store_set(model, &this_node, CONTENTS_COLUMN_CATEGORY, category_dn, -1);
char* description = entry_get_attribute_or_none(child, "description");
gtk_list_store_set(model, &this_node, CONTENTS_COLUMN_DESCRIPTION, description, -1);
}
gtk_tree_model_filter_refilter(model_filter);
}
void contents_init(GtkBuilder* builder) {
contents_view = GTK_TREE_VIEW(gtk_builder_get_object_CHECKED(builder, "contents_view"));
// Set filter func
GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER((gtk_tree_view_get_model(contents_view)));
gtk_tree_model_filter_set_visible_func(model_filter, contents_filter_func, NULL, NULL);
gtk_tree_model_filter_refilter(model_filter);
}

40
src/contents_view.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "contents_view.h"
#include <QItemSelection>
#include <QSortFilterProxyModel>
void ContentsView::set_root_index_from_selection(const QItemSelection &selected, const QItemSelection &) {
auto indexes = selected.indexes();
if (indexes.size() == 0) {
return;
}
// Map from proxy model of given index to source model of this view (if needed)
auto source_index = indexes[0];
{
auto model = source_index.model();
auto proxy_model = qobject_cast<const QSortFilterProxyModel *>(model);
if (proxy_model != nullptr) {
source_index = proxy_model->mapToSource(source_index);
}
}
// Map from source model of this view to proxy model of this view (if needed)
auto contents_index = source_index;
{
auto model = this->model();
auto proxy_model = qobject_cast<const QSortFilterProxyModel *>(model);
if (proxy_model != nullptr) {
contents_index = proxy_model->mapFromSource(contents_index);
}
}
if (!this->model()->checkIndex(contents_index)) {
printf("ContentsView::set_root_index_from_selection received bad index!\n");
return;
}
this->setRootIndex(contents_index);
}

View File

@ -1,9 +1,18 @@
#pragma once
#include <gtk/gtk.h>
#include <QTreeView>
void contents_init(GtkBuilder* builder);
void contents_change_target(const char* new_target_dn);
void contents_populate_model();
void contents_refilter();
void contents_selection_changed_func(GtkTreeSelection* selection, gpointer user_data);
// Shows name, category and description of children of entry selected in containers view
class ContentsView : public QTreeView {
Q_OBJECT
public:
using QTreeView::QTreeView;
public slots:
void set_root_index_from_selection(const QItemSelection &selected, const QItemSelection &);
private:
};

View File

@ -1,409 +0,0 @@
#include "entry.h"
#include "containers_view.h"
#include "contents_view.h"
#include "utils.h"
#include "constants.h"
// NOTE: need to define this once in one .c file
#define STB_DS_IMPLEMENTATION
#include "stb_ds.h"
#include "active_directory.h"
#include <stdio.h>
#include <stdlib.h>
entries_map* entries;
entry* get_entry(const char* dn) {
return shget(entries, dn);
}
void entry_load(const char* dn) {
entry* e = (entry*)malloc(sizeof(entry));
e->dn = strdup(dn);
//
// Load entry's attributes
//
e->attributes = NULL;
e->attribute_keys = NULL;
char** attributes_raw = ad_get_attribute(e->dn, NULL);
if (attributes_raw != NULL) {
// Init entry's attributes map
sh_new_strdup(e->attributes);
// Load e->attributes
// attributes_raw is in the form of:
// char** array of [key, value, value, key, value ...]
// it's a map of keys to values where keys can map to multiple values
// transform it into:
// stb map of [key => [value, value ...], key => [value, value ...] ...]
for (int i = 0; attributes_raw[i + 2] != NULL; i += 2) {
char* key = attributes_raw[i];
// Get current values of this attribute
STR_ARRAY values = shget(e->attributes, key);
// Append new value to values list
arrput(values, strdup(attributes_raw[i + 1]));
// Update values in attributes map
shput(e->attributes, key, values);
}
// Load e->attributes_keys, which contains keys in their original ordering
char* current_key = NULL;
for (int i = 0; attributes_raw[i + 2] != NULL; i += 2) {
char* key = attributes_raw[i];
if (current_key == NULL || !streql(current_key, key)) {
// Encountered a new key, save it
arrput(e->attribute_keys, strdup(key));
current_key = key;
}
}
// Free attributes_raw
for (int i = 0; attributes_raw[i] != NULL; i++) {
free(attributes_raw[i]);
}
free(attributes_raw);
}
//
// Load children list
//
e->children = NULL;
char** children_dns = ad_list(e->dn);
if (children_dns != NULL) {
for (int i = 0; children_dns[i] != NULL; i++) {
char* child_dn = children_dns[i];
arrput(e->children, strdup(child_dn));
// Preload children
entry_load(child_dn);
}
free(children_dns);
}
// Add entry to entries map
shput(entries, e->dn, e);
}
void entry_init() {
// Init map to use strdup for string allocation
sh_new_strdup(entries);
// Load entries recursively
entry_load(HEAD_DN);
}
STR_ARRAY entry_get_attribute(entry* e, const char* key) {
return shget(e->attributes, key);
}
char* entry_get_attribute_or_none(entry* e, const char* key) {
static char* none_str = "none";
STR_ARRAY attribute_array = entry_get_attribute(e, key);
if (attribute_array != NULL) {
return attribute_array[0];
} else {
return none_str;
}
}
bool entry_attribute_exists(entry* e, const char* key, const char* value) {
STR_ARRAY values = shget(e->attributes, key);
if (values == NULL) {
return false;
} else {
for (int i = 0; i < arrlen(values); i++) {
if (streql(values[i], value)) {
return true;
}
}
return false;
}
}
void entry_delete(entry* e) {
int result = ad_object_delete(e->dn);
if (result == AD_SUCCESS) {
// Delete entry from parent's child list
// Get parent dn by cutting part before first comma
// TODO: get this dn in a better way
char* dn = e->dn;
const char* comma = strchr(dn, ',');
char parent_dn[DN_LENGTH_MAX];
strncpy(parent_dn, comma, DN_LENGTH_MAX);
entry* parent = shget(entries, parent_dn);
if (parent != NULL) {
// Find child index in children array
STR_ARRAY children = parent->children;
int delete_i = -1;
for (int i = 0; i < arrlen(children); i++) {
char* child = children[i];
if (streql(child, dn)) {
delete_i = i;
break;
}
}
if (delete_i != -1) {
arrdel(children, delete_i);
}
}
//
// Free entry and all it's members
//
shdel(entries, e->dn);
free(e->dn);
// Free attributes
for (int i = 0; i < shlen(e->attributes); i++) {
STR_ARRAY values = e->attributes[i].value;
for (int j = 0; j < arrlen(values); j++) {
char* attribute = values[j];
free(attribute);
}
arrfree(values);
}
shfree(e->attributes);
// Free attribute_keys
for (int i = 0; i < arrlen(e->attribute_keys); i++) {
char* key = e->attribute_keys[i];
free(key);
}
arrfree(e->attribute_keys);
// Free children
for (int i = 0; i < arrlen(e->children); i++) {
free(e->children[i]);
}
arrfree(e->children);
// Replicate changes in view models
containers_populate_model();
contents_populate_model();
} else {
printf("ad_object_delete error: %s\n", ad_get_error());
}
}
// TODO: probably need to add more objectClasses
bool entry_is_container(entry* e) {
static const char* container_objectClasses[4] = {"container", "organizationalUnit", "builtinDomain", "domain"};
int container_objectClasses_size = sizeof(container_objectClasses) / sizeof(container_objectClasses[0]);
for (int i = 0; i < container_objectClasses_size; i++) {
if (entry_attribute_exists(e, "objectClass", container_objectClasses[i])) {
return true;
}
}
return false;
}
// "OU=Something,CN=Blah,CN=Bleh" => "Something"
void first_element_in_dn(char* buffer, const char* dn, size_t buffer_size) {
if (buffer == NULL) {
return;
}
*buffer = '\0';
if (dn == NULL) {
return;
}
// Remove part before first "="
const char* equals_ptr = strchr(dn, '=');
if (equals_ptr == NULL || strlen(equals_ptr) <= 1) {
return;
}
strncpy(buffer, equals_ptr + 1, buffer_size);
// Remove part after first ","
char* comma_ptr = strchr(buffer, ',');
if (comma_ptr == NULL) {
return;
}
*comma_ptr = '\0';
}
// NOTE: doesn't handle multi-valued for now
bool entry_edit_value(entry* e, char* key, char* new_value) {
int result = ad_mod_replace(e->dn, key, new_value);
STR_ARRAY values = entry_get_attribute(e, key);
if (result == AD_SUCCESS) {
free(values[0]);
values[0] = strdup(new_value);
return true;
} else {
printf("ad_mod_replace error: %s\n", ad_get_error());
return false;
}
}
void add_fake_attribute(entry* e, const char* key, const char* value) {
STR_ARRAY values = NULL;
arrput(values, strdup(value));
shput(e->attributes, key, values);
}
entry* make_fake_entry(const char* name, entry* parent, bool container, const char* category) {
entry* e = (entry*)malloc(sizeof(entry));
char dn[1000];
if (parent == NULL) {
strcpy(dn, HEAD_DN);
} else {
strcpy(dn, "CN=");
strcat(dn, name);
strcat(dn, ",");
strcat(dn, parent->dn);
}
e->dn = strdup(dn);
e->attributes = NULL;
sh_new_strdup(e->attributes);
e->attribute_keys = NULL;
e->children = NULL;
shput(entries, dn, e);
if (parent != NULL) {
arrput(parent->children, strdup(e->dn));
}
add_fake_attribute(e, "name", name);
if (container) {
add_fake_attribute(e, "objectClass", "container");
} else {
add_fake_attribute(e, "objectClass", "class");
}
add_fake_attribute(e, "objectCategory", category);
for (int i = 0; i < shlen(e->attributes); i++) {
attributes_map a = e->attributes[i];
arrput(e->attribute_keys, strdup(a.key));
}
return e;
}
void entry_init_fake() {
// Init map to use strdup for string allocation
sh_new_strdup(entries);
// Load entries recursively
entry_load(HEAD_DN);
// HEAD_DN= "DC=domain,DC=alt"
entry* head = make_fake_entry(HEAD_DN, NULL, true, "Person");
entry* dave = make_fake_entry("dave", head, true, "Person");
make_fake_entry("daves_dog", dave, false, "Robot");
make_fake_entry("daves_car", dave, false, "Robot");
add_fake_attribute(dave, "description", "dave is a cool dude");
entry* mark = make_fake_entry("mark", head, true, "Person");
make_fake_entry("marks_son", mark, false, "Robot");
make_fake_entry("marks_daughter", mark, false, "Robot");
entry* alice = make_fake_entry("alice", head, true, "Person");
make_fake_entry("alices_child", alice, false, "Robot");
make_fake_entry("alices_son", alice, false, "Person");
make_fake_entry("alices_second_son", alice, false, "Person");
entry* advanced_bob = make_fake_entry("advanced_bob", head, true, "Person");
make_fake_entry("advanced_bobs_son", advanced_bob, false, "Robot");
make_fake_entry("advanced_bobs_daughter", advanced_bob, false, "Robot");
add_fake_attribute(advanced_bob, "showInAdvancedViewOnly", "TRUE");
entry* advanced_klaren = make_fake_entry("advanced_klaren", head, true, "Person");
make_fake_entry("advanced_klarens_son", advanced_klaren, false, "Robot");
make_fake_entry("advanced_klarens_daughter", advanced_klaren, false, "Robot");
add_fake_attribute(advanced_klaren, "showInAdvancedViewOnly", "TRUE");
}
bool entry_new(const char* name, NewEntryType type) {
return;
// TODO: compose parent DN
// Users will be "HEAD_DN" + Users
// Computers will be "HEAD_DN" + Computers
char parent_dn[DN_LENGTH_MAX];
switch (type) {
case NewEntryType_User: {
snrpintf("CN=Users,%s", DN_LENGTH_MAX, HEAD_DN);
break;
}
case NewEntryType_Computer: {
snrpintf("CN=Computers,%s", DN_LENGTH_MAX, HEAD_DN);
break;
}
case NewEntryType_OU: {
snrpintf("CN=Computers,%s", DN_LENGTH_MAX, HEAD_DN);
break;
}
case NewEntryType_Group: {
break;
}
}
entry* parent = shget(entries, parent_dn);
char entry_dn[DN_LENGTH_MAX];
snrpintf("CN=%s,%s", DN_LENGTH_MAX, name, parent->dn);
int result;
switch (type) {
case NewEntryType_User: {
result = ad_create_user(name, entry_dn);
break;
}
case NewEntryType_Computer: {
result = ad_create_computer(name, entry_dn);
break;
}
case NewEntryType_OU: {
result = ad_ou_create(name, entry_dn);
break;
}
case NewEntryType_Group: {
result = ad_group_create(name, entry_dn);
break;
}
}
if (result == AD_SUCCESS) {
entry_load(entry_dn);
// Add new netry to parent's children
STR_ARRAY parent_children = parent->children;
arrput(parent_children, strdup(entry_dn));
// Replicate changes in view models
containers_populate_model();
contents_populate_model();
} else {
// TODO: display error in GUI
printf("new entry error: %s\n", ad_get_error());
}
}

View File

@ -1,48 +0,0 @@
#ifndef ENTRY_H
#define ENTRY_H
#include <stdbool.h>
#include <stddef.h>
// for use with stb array functions
typedef char** STR_ARRAY;
typedef struct attributes_map {
char* key;
STR_ARRAY value;
} attributes_map;
typedef struct entry {
char* dn;
attributes_map* attributes;
STR_ARRAY attribute_keys;
STR_ARRAY children;
} entry;
typedef struct entries_map {
char* key;
entry* value;
} entries_map;
typedef enum {
NewEntryType_User,
NewEntryType_Computer,
NewEntryType_OU,
NewEntryType_Group
} NewEntryType;
void entry_init();
void entry_init_fake();
void entry_load(const char* dn);
STR_ARRAY entry_get_attribute(entry* e, const char* key);
char* entry_get_attribute_or_none(entry* e, const char* key);
bool entry_new(const char* name, NewEntryType type);
entry* get_entry(const char* dn);
void entry_delete(entry* e);
bool entry_attribute_exists(entry* e, const char* key, const char* value);
bool entry_is_container(entry* e);
void first_element_in_dn(char* buffer, const char* dn, size_t buffer_size);
bool entry_edit_value(entry* e, char* key, char* new_value);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,65 +0,0 @@
// #include "mainwindow.h"
#include "containers_view.h"
#include "contents_view.h"
#include "attributes_view.h"
#include "menu_bar.h"
#include "entry.h"
#include "utils.h"
#include "active_directory.h"
#include <gtk/gtk.h>
int main(int argc, char** argv) {
// TODO: show some kind of progress indicator before loading data? it's not instant
bool fake_entries = false;
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "fake") == 0) {
fake_entries = true;
}
}
if (fake_entries) {
entry_init_fake();
} else {
// Login into ldap
LDAP* ldap_connection = ad_login();
if (ldap_connection == NULL) {
printf("ad_login error: %s\n", ad_get_error());
return 0;
}
// Load entry data
entry_init();
}
// Setup UI
gtk_init(&argc, &argv);
// Load builder
GtkBuilder *builder = gtk_builder_new_from_file("data/adtool.glade");
if (builder == NULL) {
printf("Failed to load glade file\n");
return 0;
}
// NOTE: inits have to be in this order
attributes_init(builder);
contents_init(builder);
containers_init(builder);
menu_bar_init(builder);
gtk_builder_connect_signals(builder, NULL);
GtkWidget* window = GTK_WIDGET(gtk_builder_get_object_CHECKED(builder, "window"));
gtk_widget_show(window);
g_object_unref(G_OBJECT(builder));
gtk_main();
return 0;
}

81
src/main.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "containers_view.h"
#include "contents_view.h"
#include "attributes_view.h"
#include "ad_filter.h"
#include "ad_model.h"
#include "attributes_model.h"
#include "ui_mainwindow.h"
#include "ad_interface.h"
#include <QApplication>
#include <QString>
int main(int argc, char **argv) {
// Load fake AD data if given "fake" argument
// This also swaps all ad interface functions to their fake versions (including login)
if (argc >= 1 && QString(argv[0]) == "fake") {
FAKE_AD = true;
}
if (!ad_interface_login()) {
return 1;
}
QApplication app(argc, argv);
//
// Setup ui
//
Ui::MainWindow ui;
QMainWindow main_window;
ui.setupUi(&main_window);
AdModel ad_model;
// Attributes
{
auto attributes_model = new AttributesModel();
ui.attributes_view->setModel(attributes_model);
}
// Contents
{
auto view = ui.contents_view;
auto proxy = new AdFilter(ui.menubar_view_advancedView);
proxy->setSourceModel(&ad_model);
view->setModel(proxy);
}
// Containers
{
auto view = ui.containers_view;
auto proxy = new AdFilter(ui.menubar_view_advancedView, true);
proxy->setSourceModel(&ad_model);
view->setModel(proxy);
// NOTE: have to hide columns after setting model
view->hideColumn(AdModel::Column::Category);
view->hideColumn(AdModel::Column::Description);
}
//
// Connect signals
//
// Set root index of contents view to selection of containers view
QObject::connect(
ui.containers_view->selectionModel(), &QItemSelectionModel::selectionChanged,
ui.contents_view, &ContentsView::set_root_index_from_selection);
// Set target of attributes view to selection of contents view
QObject::connect(
ui.contents_view->selectionModel(), &QItemSelectionModel::selectionChanged,
ui.attributes_view, &AttributesView::set_target_from_selection);
main_window.show();
return app.exec();
}

View File

@ -1,46 +0,0 @@
#include "menu_bar.h"
#include "containers_view.h"
#include "contents_view.h"
#include "entry.h"
#include "utils.h"
bool advanced_view = false;
GtkDialog* new_user_dialog = NULL;
GtkEntry* new_user_name_entry = NULL;
bool advanced_view_is_on() {
return advanced_view;
}
void advanced_view_toggled_func(GtkWidget *widget, gpointer data) {
advanced_view = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
containers_refilter();
contents_refilter();
}
void menu_bar_file_new_user_func(GtkWidget *widget, gpointer data) {
// TODO: set max chars for entry (in glade) based on ldap limits
int response = gtk_dialog_run(new_user_dialog);
if (response == GTK_RESPONSE_OK) {
printf("new user is %s\n", gtk_entry_get_text(new_user_name_entry));
entry_new(gtk_entry_get_text(new_user_name_entry), NewEntryType_User);
gtk_entry_set_text(new_user_name_entry, "");
gtk_widget_hide(GTK_WIDGET(new_user_dialog));
} else if (response == GTK_RESPONSE_CANCEL) {
gtk_entry_set_text(new_user_name_entry, "");
gtk_widget_hide(GTK_WIDGET(new_user_dialog));
} else {
printf("ERROR: menu_bar_file_new_user_func() received unknown button response %d !\n", response);
}
}
void menu_bar_init(GtkBuilder* builder) {
new_user_dialog = GTK_DIALOG(gtk_builder_get_object_CHECKED(builder, "new_user_dialog"));
new_user_name_entry = GTK_ENTRY(gtk_builder_get_object_CHECKED(builder, "new_user_dialog_name_entry"));
}

View File

@ -1,7 +0,0 @@
#pragma once
#include <stdbool.h>
#include <gtk/gtk.h>
bool advanced_view_is_on();
void menu_bar_init(GtkBuilder* builder);

142
src/ui/ui_mainwindow.h Normal file
View File

@ -0,0 +1,142 @@
/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 5.12.8
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H
#include <QtCore/QVariant>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QSplitter>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QWidget>
#include "src/attributes_view.h"
#include "src/containers_view.h"
#include "src/contents_view.h"
QT_BEGIN_NAMESPACE
class Ui_MainWindow
{
public:
QAction *actionWhat;
QAction *actionSecond;
QAction *actionSomething;
QAction *actionHere;
QAction *menubar_view_advancedView;
QWidget *centralwidget;
QSplitter *splitter;
ContainersView *containers_view;
ContentsView *contents_view;
AttributesView *attributes_view;
QMenuBar *menubar;
QMenu *menuhello;
QMenu *menuEdit;
QMenu *menuView;
QStatusBar *statusbar;
void setupUi(QMainWindow *MainWindow)
{
if (MainWindow->objectName().isEmpty())
MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
MainWindow->resize(1307, 637);
actionWhat = new QAction(MainWindow);
actionWhat->setObjectName(QString::fromUtf8("actionWhat"));
actionSecond = new QAction(MainWindow);
actionSecond->setObjectName(QString::fromUtf8("actionSecond"));
actionSomething = new QAction(MainWindow);
actionSomething->setObjectName(QString::fromUtf8("actionSomething"));
actionHere = new QAction(MainWindow);
actionHere->setObjectName(QString::fromUtf8("actionHere"));
menubar_view_advancedView = new QAction(MainWindow);
menubar_view_advancedView->setObjectName(QString::fromUtf8("menubar_view_advancedView"));
menubar_view_advancedView->setCheckable(true);
centralwidget = new QWidget(MainWindow);
centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
splitter = new QSplitter(centralwidget);
splitter->setObjectName(QString::fromUtf8("splitter"));
splitter->setGeometry(QRect(0, 0, 1301, 591));
splitter->setOrientation(Qt::Horizontal);
containers_view = new ContainersView(splitter);
containers_view->setObjectName(QString::fromUtf8("containers_view"));
containers_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
containers_view->setRootIsDecorated(true);
containers_view->setItemsExpandable(true);
containers_view->setExpandsOnDoubleClick(true);
splitter->addWidget(containers_view);
containers_view->header()->setVisible(true);
contents_view = new ContentsView(splitter);
contents_view->setObjectName(QString::fromUtf8("contents_view"));
contents_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
contents_view->setSelectionMode(QAbstractItemView::SingleSelection);
contents_view->setRootIsDecorated(false);
contents_view->setItemsExpandable(false);
contents_view->setExpandsOnDoubleClick(false);
splitter->addWidget(contents_view);
contents_view->header()->setVisible(true);
attributes_view = new AttributesView(splitter);
attributes_view->setObjectName(QString::fromUtf8("attributes_view"));
attributes_view->setEditTriggers(QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed);
attributes_view->setSelectionMode(QAbstractItemView::NoSelection);
attributes_view->setSelectionBehavior(QAbstractItemView::SelectRows);
splitter->addWidget(attributes_view);
MainWindow->setCentralWidget(centralwidget);
menubar = new QMenuBar(MainWindow);
menubar->setObjectName(QString::fromUtf8("menubar"));
menubar->setGeometry(QRect(0, 0, 1307, 27));
menuhello = new QMenu(menubar);
menuhello->setObjectName(QString::fromUtf8("menuhello"));
menuEdit = new QMenu(menubar);
menuEdit->setObjectName(QString::fromUtf8("menuEdit"));
menuView = new QMenu(menubar);
menuView->setObjectName(QString::fromUtf8("menuView"));
MainWindow->setMenuBar(menubar);
statusbar = new QStatusBar(MainWindow);
statusbar->setObjectName(QString::fromUtf8("statusbar"));
MainWindow->setStatusBar(statusbar);
menubar->addAction(menuhello->menuAction());
menubar->addAction(menuEdit->menuAction());
menubar->addAction(menuView->menuAction());
menuhello->addAction(actionWhat);
menuhello->addAction(actionSecond);
menuEdit->addAction(actionSomething);
menuEdit->addAction(actionHere);
menuView->addAction(menubar_view_advancedView);
retranslateUi(MainWindow);
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
void retranslateUi(QMainWindow *MainWindow)
{
MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", nullptr));
actionWhat->setText(QApplication::translate("MainWindow", "What", nullptr));
actionSecond->setText(QApplication::translate("MainWindow", "Second", nullptr));
actionSomething->setText(QApplication::translate("MainWindow", "Something", nullptr));
actionHere->setText(QApplication::translate("MainWindow", "Here", nullptr));
menubar_view_advancedView->setText(QApplication::translate("MainWindow", "Advanced view", nullptr));
menuhello->setTitle(QApplication::translate("MainWindow", "New", nullptr));
menuEdit->setTitle(QApplication::translate("MainWindow", "Edit", nullptr));
menuView->setTitle(QApplication::translate("MainWindow", "View", nullptr));
} // retranslateUi
};
namespace Ui {
class MainWindow: public Ui_MainWindow {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_MAINWINDOW_H

View File

@ -1,22 +0,0 @@
#include "utils.h"
#include <string.h>
#include <stdbool.h>
bool streql(const char* a, const char* b) {
return strcmp(a, b) == 0;
}
// Use this instead of regular gtk_builder_get_object() so that
// on error the object's name is printed
GObject* gtk_builder_get_object_CHECKED(GtkBuilder* builder, const char *name) {
GObject* object = gtk_builder_get_object(builder, name);
if (object != NULL) {
return object;
} else {
printf("ERROR: gtk_builder_get_object() failed to get \"%s\"\n", name);
return NULL;
}
}

View File

@ -1,7 +0,0 @@
#pragma once
#include <stdbool.h>
#include <gtk/gtk.h>
bool streql(const char* a, const char* b);
GObject* gtk_builder_get_object_CHECKED(GtkBuilder* builder, const char *name);