├── .coveragerc ├── .gitignore ├── .project ├── CHANGES.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── config_writer ├── __init__.py ├── config_writer.py └── setup.py ├── development.ini ├── production.ini ├── pyramid.wsgi ├── pytest.ini ├── samba4_config_writer ├── __init__.py └── setup.py ├── samba4_manager ├── __init__.py ├── authentication.py ├── authorization.py ├── model.py ├── static │ ├── _cards.scss │ ├── _footer.scss │ ├── _global.scss │ ├── _login.scss │ ├── _mixins.scss │ ├── _navbar.scss │ ├── _utilities.scss │ ├── _variables.scss │ ├── demo │ │ ├── chart-area-demo.js │ │ ├── chart-bar-demo.js │ │ ├── chart-pie-demo.js │ │ └── datatables-demo.js │ ├── sb-admin.css │ ├── sb-admin.js │ ├── sb-admin.min.css │ ├── sb-admin.min.js │ ├── sb-admin.scss │ └── vendor │ │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ │ ├── chart.js │ │ ├── Chart.bundle.js │ │ ├── Chart.bundle.min.js │ │ ├── Chart.js │ │ └── Chart.min.js │ │ ├── datatables │ │ ├── dataTables.bootstrap4.css │ │ ├── dataTables.bootstrap4.js │ │ ├── dataTables.bootstrap4.min.css │ │ ├── dataTables.bootstrap4.min.js │ │ ├── jquery.dataTables.js │ │ └── jquery.dataTables.min.js │ │ ├── fontawesome-free │ │ ├── css │ │ │ └── all.min.css │ │ └── webfonts │ │ │ ├── fa-brands-400.eot │ │ │ ├── fa-brands-400.svg │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-brands-400.woff │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-regular-400.eot │ │ │ ├── fa-regular-400.svg │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-regular-400.woff │ │ │ ├── fa-regular-400.woff2 │ │ │ ├── fa-solid-900.eot │ │ │ ├── fa-solid-900.svg │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff │ │ │ └── fa-solid-900.woff2 │ │ ├── jquery-easing │ │ ├── jquery.easing.compatibility.js │ │ ├── jquery.easing.js │ │ └── jquery.easing.min.js │ │ ├── jquery │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ ├── jquery.min.map │ │ ├── jquery.slim.js │ │ ├── jquery.slim.min.js │ │ └── jquery.slim.min.map │ │ └── jstree │ │ ├── jstree.js │ │ ├── jstree.min.js │ │ └── themes │ │ ├── default-dark │ │ ├── 32px.png │ │ ├── 40px.png │ │ ├── style.css │ │ ├── style.min.css │ │ └── throbber.gif │ │ └── default │ │ ├── 32px.png │ │ ├── 40px.png │ │ ├── style.css │ │ ├── style.min.css │ │ └── throbber.gif ├── templates │ ├── 404.jinja2 │ ├── LICENSE │ ├── agregar_usuario.jinja2 │ ├── base.jinja2 │ ├── blank.jinja2 │ ├── charts.jinja2 │ ├── editar_computadora.jinja2 │ ├── editar_share.jinja2 │ ├── editar_usuario.jinja2 │ ├── forgot-password.jinja2 │ ├── index.jinja2 │ ├── listar_avanzado.jinja2 │ ├── listar_computadoras.jinja2 │ ├── listar_grupos.jinja2 │ ├── listar_shares.jinja2 │ ├── listar_usuarios.jinja2 │ ├── login.jinja2 │ ├── register.jinja2 │ └── tables.jinja2 ├── tests.py └── views.py ├── samba_server ├── __init__.py └── setup.py └── setup.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = samba4_manager 3 | omit = samba4_manager/test* 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | /.pydevproject 106 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | samba4_manager 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.0 2 | --- 3 | 4 | - Initial version. 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.ini *.cfg *.rst 2 | recursive-include samba4_manager *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # samba4_manager 2 | User manager for Samba, which uses Samba Python bindings. Allows to perform all the operations of user and group management. 3 | 4 | ## Description 5 | 6 | This tool is a web application that will provide user management for Samba. It interacts with Samba directly through its Python bindings. 7 | 8 | It will allow for creation, modification and deletion of users and groups, with the ultimate goal to replicate the functions of the "Active Directory Users and Groups". 9 | 10 | Both Python 2.7 and 3.6 are supported for this application. 11 | 12 | ## Motivation 13 | 14 | The GUI tools available for Linux are general-purpose configuration editors such as Webmin, which don't satisfactorily handle Samba4 user management, and don't allow for the extensive modification of properties allowed by the Windows-based tool "Active Directory Users and Groups". That tool can be used from Windows to connect and manage a Samba4 domain, but there is no equivalent tool for Linux. 15 | 16 | The use of Python for the creation of this tool is to make use of the Python bindings for Samba4, which make direct use of internal Samba data structures. And it can be a good excuse to learn the Python language in the process ;-). 17 | 18 | ## Installation 19 | 20 | ### Pre-requisites 21 | 22 | The following packages have to be installed: 23 | 24 | sudo yum install samba python3-samba python3-samba-devel samba-dc-libs python3-samba-dc httpd php-fpm httpd-devel python3-devel 25 | 26 | The following python packages need to be installed: 27 | 28 | pip install mod_wsgi 29 | 30 | To find the required configuration that is needed for Apache to be configured to load mod_wsgi as a module, run this: 31 | 32 | /usr/local/bin/mod_wsgi-express module-config 33 | 34 | ### Package installation with pip 35 | 36 | For now, the process to release the application as a Python package nas not been completed. To deploy this application, the following steps need to be performed: 37 | 38 | 1. git clone of this repository into a folder available to the web server. 39 | 2. pip install -e . 40 | 3. Configure to launch from either the web server or the standalone tool "pserve" 41 | 42 | ### As a HTTP module 43 | 44 | This example configuration applies to the Apache web server: 45 | 46 | ``` 47 | # Use only 1 Python sub-interpreter. Multiple sub-interpreters 48 | # play badly with C extensions. See 49 | # http://stackoverflow.com/a/10558360/209039 50 | WSGIApplicationGroup %{GLOBAL} 51 | WSGIPassAuthorization On 52 | WSGIDaemonProcess pyramid user=unbound group=unbound threads=4 \ 53 | python-path=/opt/rh/rh-python36/root/usr/bin/python 54 | WSGIScriptAlias /samba4_manager /var/www/html/samba4_manager/pyramid.wsgi 55 | 56 | 57 | WSGIProcessGroup pyramid 58 | Require all granted 59 | 60 | ``` 61 | 62 | Python 3.6 is not necessarily required, the application can run just as well using Python 2.7. 63 | 64 | ### As a standalone application 65 | 66 | You can run the application from the command-line using the command below. It will launch a web server process that will listen on port 6543: 67 | 68 | ``` 69 | /usr/bin/pserve development.ini --reload 70 | ``` 71 | -------------------------------------------------------------------------------- /config_writer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daxcurson/samba4_manager/a799df9007ed84e3b34929f5fc353359627e737b/config_writer/__init__.py -------------------------------------------------------------------------------- /config_writer/config_writer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from secureconfig.secureconfigparser import SecureConfigParser 3 | from secureconfig.cryptkeeper import CryptKeeper 4 | 5 | def main(): 6 | # Main function. 7 | parser=argparse.ArgumentParser("Generates encrypted credentials") 8 | parser.add_argument('--secret-file',help='Name of the file to store the secret encrypted credentials',required=True) 9 | parser.add_argument('--unencrypted-credentials',help='Name of the file that currently stores unencrypted credentials') 10 | parser.add_argument('--key-file',help="Specifies the name of the file where to store the secret key that will unlock the secret file",required=True) 11 | args=vars(parser.parse_args()) 12 | ck=CryptKeeper() 13 | key=ck.generate_key() 14 | # Now, if we received the argument generate-key, let's generate the file 15 | scfg=SecureConfigParser.from_key(key) 16 | scfg.read(args["unencrypted_credentials"]) 17 | username=scfg.get('credentials','username') 18 | password=scfg.get('credentials','password') 19 | scfg.set('credentials','username',username,encrypt=True) 20 | scfg.set('credentials','password',password,encrypt=True) 21 | file=open(args['secret_file'],"w") 22 | scfg.write(file) 23 | file.close() 24 | # Now we store the secret key 25 | secret_file=open(args['key_file'],"w") 26 | secret_file.write(key) 27 | secret_file.close() 28 | # Invoke main function upon execution 29 | if __name__=="__main__": 30 | main() -------------------------------------------------------------------------------- /config_writer/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name="config_writer") -------------------------------------------------------------------------------- /development.ini: -------------------------------------------------------------------------------- 1 | ### 2 | # app configuration 3 | # https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html 4 | ### 5 | 6 | [app:main] 7 | use = egg:samba4-manager 8 | 9 | pyramid.reload_templates = true 10 | pyramid.debug_authorization = true 11 | pyramid.debug_notfound = true 12 | pyramid.debug_routematch = false 13 | pyramid.default_locale_name = en 14 | pyramid.includes = 15 | pyramid_debugtoolbar 16 | 17 | # By default, the toolbar only appears for clients from IP addresses 18 | # '127.0.0.1' and '::1'. 19 | debugtoolbar.hosts = 127.0.0.1 ::1 192.168.1.1 20 | 21 | ### 22 | # wsgi server configuration 23 | ### 24 | 25 | [server:main] 26 | use = egg:waitress#main 27 | listen = *:6543 28 | 29 | ### 30 | # logging configuration 31 | # https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html 32 | ### 33 | 34 | [loggers] 35 | keys = root, samba4_manager 36 | 37 | [handlers] 38 | keys = console 39 | 40 | [formatters] 41 | keys = generic 42 | 43 | [logger_root] 44 | level = INFO 45 | handlers = console 46 | 47 | [logger_samba4_manager] 48 | level = DEBUG 49 | handlers = 50 | qualname = samba4_manager 51 | 52 | [handler_console] 53 | class = StreamHandler 54 | args = (sys.stderr,) 55 | level = NOTSET 56 | formatter = generic 57 | 58 | [formatter_generic] 59 | format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s 60 | -------------------------------------------------------------------------------- /production.ini: -------------------------------------------------------------------------------- 1 | ### 2 | # app configuration 3 | # https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html 4 | ### 5 | 6 | [app:main] 7 | use = egg:samba4_manager 8 | 9 | pyramid.reload_templates = false 10 | pyramid.debug_authorization = true 11 | pyramid.debug_notfound = false 12 | pyramid.debug_routematch = false 13 | pyramid.default_locale_name = en 14 | 15 | ### 16 | # wsgi server configuration 17 | ### 18 | 19 | [server:main] 20 | use = egg:waitress#main 21 | listen = *:6543 22 | 23 | ### 24 | # logging configuration 25 | # https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html 26 | ### 27 | 28 | [loggers] 29 | keys = root, samba4_manager 30 | 31 | [handlers] 32 | keys = console 33 | 34 | [formatters] 35 | keys = generic 36 | 37 | [logger_root] 38 | level = WARN 39 | handlers = console 40 | 41 | [logger_samba4_manager] 42 | level = WARN 43 | handlers = 44 | qualname = samba4_manager 45 | 46 | [handler_console] 47 | class = StreamHandler 48 | args = (sys.stderr,) 49 | level = NOTSET 50 | formatter = generic 51 | 52 | [formatter_generic] 53 | format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s 54 | -------------------------------------------------------------------------------- /pyramid.wsgi: -------------------------------------------------------------------------------- 1 | from pyramid.paster import get_app, setup_logging 2 | ini_path = '/var/www/html/samba4_manager/development.ini' 3 | setup_logging(ini_path) 4 | application = get_app(ini_path, 'main') 5 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = samba4_manager 3 | python_files = *.py 4 | -------------------------------------------------------------------------------- /samba4_config_writer/__init__.py: -------------------------------------------------------------------------------- 1 | from reconfigure.configs import SambaConfig 2 | 3 | class SambaConfigWriter(object): 4 | def __init__(self): 5 | self.config_file="/etc/samba/smb.conf" 6 | self.config=SambaConfig(path=self.config_file) 7 | self.config.load() 8 | def listar_shares(self): 9 | return self.config.tree.shares 10 | def obtener_share(self,nombre): 11 | # Recorramos la lista de shares y extraigamos aquella que nos sirve. 12 | share_encontrada=() 13 | encontrado=False 14 | cant_shares=len(self.config.tree.shares) 15 | share_actual=0 16 | while(share_actual input, 19 | .form-label-group > label { 20 | padding: var(--input-padding-y) var(--input-padding-x); 21 | height: auto; 22 | } 23 | 24 | .form-label-group > label { 25 | position: absolute; 26 | top: 0; 27 | left: 0; 28 | display: block; 29 | width: 100%; 30 | margin-bottom: 0; 31 | /* Override default `