Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
fea2eab
* Add initial Travis-CI test suite
Jul 13, 2015
91b2a79
* Remove pypy from test suite, until I can get tests running fine
Jul 13, 2015
670c0d7
* Added initial setup.py and empty __init__.py files
Jul 13, 2015
8278377
* Changed the import name from app to ldapass in tests
Jul 13, 2015
2fae810
* Added installing ldapass to .travis.yml
Jul 13, 2015
1bdeb78
* Fix setup.py formatting
Jul 13, 2015
c242463
* Add missing coma in setup.py
Jul 13, 2015
86c511c
* Removed incorrectly used quotes around __name__ in ldapass.py
Jul 13, 2015
0443981
* Refactoring non pythonic db lenght assessment with more pythonic one
Jul 13, 2015
de9a777
* Remove line installing requirements via pip, since the ldapass inst…
Jul 13, 2015
3da39af
* Switch tests to pytest
Jul 13, 2015
9de66b2
* Fix py.test name in .travis.yml
Jul 13, 2015
882bcd4
* Rename main test file to follow pytest discovery rules
Jul 13, 2015
b91b3f2
* Replace pytest with nosetest to see if tests will work again
Jul 13, 2015
ba57c84
* Fix nosetests name
Jul 13, 2015
516f92f
* Added DEBUG and TESTING to Flask config in tests
Jul 13, 2015
318fd54
* Change DEBUG and TESTING to testing=True in tests
Jul 13, 2015
1343272
* Replace nosetests with py.test
Jul 13, 2015
983966e
* Added pytest import and fixture for app setup
Jul 13, 2015
58b1a57
* Remove 'if __name__' from tests
Jul 13, 2015
9b1da82
* Add debug=True to solve the pytest failing issue
Jul 14, 2015
52cd4b8
* Rewrite of the tests to be more pytest-like and less unittest-like
Jul 14, 2015
021f191
* Forgot to replace self with test_client in testGetHtmlForm()
Jul 14, 2015
2868ab2
* Added some valid content to __init__.py file
Jul 14, 2015
f413777
* Minor .travis.yml adjustments
Jul 14, 2015
89e04ef
* Change testGetHtmlForm() test name to more pythonic form
Jul 14, 2015
c3c4870
* Test removing requirements installation to see if it made tests fai…
Jul 14, 2015
58cf118
* Add -e to pip install to test if it fixes the tests
Jul 14, 2015
5a5e302
* Added MANIFEST.in for installing flask templates and static files
Jul 14, 2015
774c904
* Remove MANIFEST.in and instead use package_data in setup.py
Jul 14, 2015
920150f
* Fix package_data entries in setup.py
Jul 14, 2015
7179061
* Created .gitignore file for .pyc and vim temp files
Aug 7, 2015
6d739cb
* Added build status icon for TravisCI tests
Aug 7, 2015
c5867b0
* Update Python module requirements in requirements.txt
bartekrutkowski Dec 10, 2016
fe9be24
* Update example Nginx configuration
bartekrutkowski Dec 10, 2016
da8f85f
* Fix and update installation instructions in README.md
bartekrutkowski Dec 10, 2016
fbdac1b
* Change 'l' var name to 'ldap_conn' for better readability
bartekrutkowski Dec 10, 2016
995fe4a
* Add missing environmental variable setup in example uwsgi_ldapass.i…
bartekrutkowski Dec 10, 2016
ade1575
* Fix typo in README.md file
bartekrutkowski Dec 10, 2016
0849091
* Fix static asset path with Jinja generator in templates/base.html
bartekrutkowski Dec 10, 2016
6c4df00
* Move ldap_debug config var from [app] section to [ldap] and rename …
bartekrutkowski Dec 11, 2016
ceeebd3
* Add debug variable in [app] config section
bartekrutkowski Dec 11, 2016
aa1e85e
* Move ldap.initialize outise of the try block because it cant fail
bartekrutkowski Dec 11, 2016
f5b7231
* Add [ldap] starttls bool variable and change ldap_conn.start_tls_s(…
bartekrutkowski Dec 11, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.pyc
*~
*.swp
14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
language: python

python:
- 2.7

install:
- pip install .

env:
global:
- LDAPASS_CONFIG=tests/ldapass.conf

script:
- py.test
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
ldapass
ldapass [![Build Status](https://travis-ci.org/bartekrutkowski/ldapass.svg?branch=master)](https://travis-ci.org/bartekrutkowski/ldapass)
=======
**Web application for setting/changing LDAP user passwords.**

LDAPass is a Python/Flask simple web application that aims to make Unix/Linux LDAP user account passwords manageable by their users without involvement of DevOps/SysAdmin resources in a simple, non complicated application not requiring extensive setup nor maintenance.
LDAPass is a Python/Flask simple web application that aims to make Unix/Linux user account LDAP passwords manageable by their owners without involvement of DevOps/SysAdmin resources in a simple application not requiring extensive setup or maintenance.

## Requirements

To host LDAPass on a Unix/Linux system you need the following software installed:
To run LDAPass on a Unix/Linux system you need the following software installed:

- Python 2.7.x with following modules installed:
- Flask
- python-ldap
- Python-LDAP
- WTForms
- SQLite3
- A web server (Nginx, Apache etc., example Nginx configuration is provided)
- UWSGI 2.x
- Application Server Container (example UWSGI 2.x configuration is provided)

## Installation

The following examples are assuming you are deploying LDAPass on a FreeBSD system, you should adjust your paths accordingly to your OS.

Clone the repository into proper location:
Clone the repository to the chosen location:

```sh
$ git clone git@github.com:bartekrutkowski/ldapass.git /var/www/ldapass
$ git clone https://github.com/bartekrutkowski/ldapass.git /var/www/ldapass
```

Copy the config ldapass.conf configuration file and edit it with appropriate values:
Copy the example ldapass.conf configuration file to main app directory and edit it with appropriate values:

```sh
$ cp /var/www/ldapass/examples/ldapass.conf /var/www/ldapass/ldapass/
$ cp /var/www/ldapass/examples/ldapass.conf /var/www/ldapass/ldapass/ldapass.conf
$ vi /var/www/ldapass/ldapass/ldapass.conf
```

Expand All @@ -39,7 +40,7 @@ $ cp /var/www/ldapass/examples/nginx_ldapass.example.com.conf /usr/local/etc/ngi
$ vi /usr/local/etc/nginx/conf.d/ldapass.example.com.conf
```

Copy the UWSGI uwsgi_ldapass.ini configuration file into your OR UWSGI config directory and edit it with appropriate values:
Copy the UWSGI uwsgi_ldapass.ini configuration file into your OS UWSGI config directory and edit it with appropriate values:

```sh
$ cp /var/www/ldapass/examples/uwsgi_ldapass.ini /usr/local/etc/uwsgi_ldapass.ini
Expand Down
4 changes: 3 additions & 1 deletion examples/ldapass.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ listen_port = 8080
smtp_addr = smtp-server.example.com
hostname = ldapass-hostname.example.com
database = ldapass.sql
ldap_debug = 0
debug = False

[ldap]
addr = ldap-server.example.com
port = 389
starttls = True
user = cn=Manager,dc=example,dc=com
pass = very-complicated-ldap-password
basedn = ou=People,dc=example,dc=com
debug = 0
6 changes: 3 additions & 3 deletions examples/nginx_ldapass.example.com.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ server {
error_log /var/log/nginx/ldapass.example.com-error.log;

location /static {
alias /var/www/ldapass/app/static;
alias /var/www/ldapass/ldapass/static;
}

location / { try_files $uri @ldapass.example.com; }

location @ldapass.example.com {
root /var/www/ldapass/app;
root /var/www/ldapass/ldapass;
include uwsgi_params;
uwsgi_pass 127.0.0.1:8005;
}
}
}
12 changes: 6 additions & 6 deletions examples/uwsgi_ldapass.ini
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[uwsgi]
chdir = /var/www/ldapass/app
venv = /var/www/ldapass/ldapass_venv
chdir = /var/www/ldapass/ldapass
module = ldapass
callable = app
env = LDAPASS_CONFIG=/var/www/ldapass/ldapass/ldapass.conf

master = true
processes = 8
chmod-socket = 666
processes = 2
chmod-socket = 127.0.0.1:8005
vacuum = true

logto = /var/log/uwsgi/ldapass.log
logto2 = /var/log/uwsgi/ldapass2.log
logger = syslog
log-micros = true
log-format = %(addr) - %(user) [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size) "%(referer)" "%(uagent)"
4 changes: 4 additions & 0 deletions ldapass/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

__version__ = "0.1"
82 changes: 35 additions & 47 deletions ldapass/ldapass.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from wtforms import Form, TextField, PasswordField, validators


app = Flask('__name__')
app = Flask(__name__)
app.secret_key = os.urandom(128)
conf = RawConfigParser()
conf.read(os.environ['LDAPASS_CONFIG'])
Expand Down Expand Up @@ -69,22 +69,18 @@ def index():
if form.validate():
ldap_uri = 'ldap://{addr}:{port}'.format(
addr=conf.get('ldap', 'addr'), port=conf.get('ldap', 'port'))
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
ldap_conn = ldap.initialize(ldap_uri, trace_level=conf.get('ldap', 'debug'))
search_filter = 'mail={mail}'.format(mail=form.mail.data)

try:
ldap.set_option(
ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
l = ldap.initialize(
ldap_uri, trace_level=conf.get('app', 'ldap_debug'))
l.start_tls_s()
except ldap.LDAPError as error:
return render_template('index.html', error=error, form=form)
try:
search_filter = 'mail={mail}'.format(mail=form.mail.data)
ldap_result_id = l.search(
conf.get('ldap', 'basedn'), ldap.SCOPE_SUBTREE,
search_filter, None)
if conf.getboolean('ldap', 'starttls'):
ldap_conn.start_tls_s()
ldap_result_id = ldap_conn.search(conf.get('ldap', 'basedn'), ldap.SCOPE_SUBTREE, search_filter, None)
result_type, result_data = ldap_conn.result(ldap_result_id, 0)
except ldap.LDAPError as error:
return render_template('index.html', error=error, form=form)
result_type, result_data = l.result(ldap_result_id, 0)

if len(result_data) == 1:
link_id = '{uuid}-{account}'.format(
uuid=str(uuid.uuid4()),
Expand All @@ -101,10 +97,10 @@ def index():
db_curs.execute(
"INSERT INTO mails (mail, link_id, created) VALUES \
('{mail}', '{link_id}', '{created}')".format(
mail=form.mail.data,
link_id=link_id,
created=datetime.datetime.now()
))
mail=form.mail.data,
link_id=link_id,
created=datetime.datetime.now()
))
flash('Email containing password reset url has been sent \
to {mail}'.format(mail=form.mail.data))
else:
Expand All @@ -114,10 +110,10 @@ def index():
db_curs.execute(
"REPLACE INTO mails (mail, link_id, created) VALUES \
('{mail}', '{link_id}', '{created}')".format(
mail=form.mail.data,
link_id=link_id,
created=datetime.datetime.now()
))
mail=form.mail.data,
link_id=link_id,
created=datetime.datetime.now()
))
flash('Email containing password reset url has been sent \
to {mail}. Previous reset urls have been \
invalidated.'.format(mail=form.mail.data))
Expand Down Expand Up @@ -173,25 +169,19 @@ def reset(link_id):
addr=conf.get('ldap', 'addr'),
port=conf.get('ldap', 'port')
)
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
ldap_conn = ldap.initialize(ldap_uri, trace_level=conf.get('ldap', 'debug'))
search_filter = 'mail={mail}'.format(mail=db_data[0][1])

try:
ldap.set_option(
ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
l = ldap.initialize(
ldap_uri, trace_level=conf.get('app', 'ldap_debug'))
l.start_tls_s()
except ldap.LDAPError as error:
return render_template('error.html', error=error)
try:
search_filter = 'mail={mail}'.format(mail=db_data[0][1])
ldap_result_id = l.search(
conf.get('ldap', 'basedn'),
ldap.SCOPE_SUBTREE,
search_filter,
None)
result_type, result_data = l.result(ldap_result_id, 0)
l.simple_bind_s(
if conf.getboolean('ldap', 'starttls'):
ldap_conn.start_tls_s()
ldap_result_id = ldap_conn.search(conf.get('ldap', 'basedn'),
ldap.SCOPE_SUBTREE, search_filter, None)
result_type, result_data = ldap_conn.result(ldap_result_id, 0)
ldap_conn.simple_bind_s(
conf.get('ldap', 'user'), conf.get('ldap', 'pass'))
l.passwd_s(
ldap_conn.passwd_s(
'uid={uid},{basedn}'.format(
uid=result_data[0][1]['uid'][0],
basedn=conf.get('ldap', 'basedn')),
Expand All @@ -200,11 +190,8 @@ def reset(link_id):
except ldap.LDAPError as error:
error = 'LDAP error: {error}, please get in touch with \
LDAP administration.'.format(error=error)
return render_template(
'reset.html',
error=error,
form=form
)
return render_template('reset.html', error=error, form=form)

flash('Password for account {mail} has been changed.'.format(
mail=db_data[0][1]))
db_curs.execute(
Expand Down Expand Up @@ -232,7 +219,7 @@ def reset(link_id):
db_curs = db_conn.cursor()
db_curs.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='mails'")
if len(db_curs.fetchall()) == 0:
if not db_curs.fetchall():
print('WARNING: the SQLite file {database} doesnt exist! Sleeping for \
10 seconds and creating the database file. KILL ME if this is an \
error!').format(database=conf.get('app', 'database'))
Expand All @@ -241,7 +228,7 @@ def reset(link_id):
'''create table mails (
id INTEGER PRIMARY KEY,
mail VARCHAR(255) NOT NULL COLLATE NOCASE,
link_id VARCHAR(512) NOT NULL COLLATE NOCASE,
link_id VARCHAR(512) NOT NULL COLLATE NOCASE,
created INTEGER DEFAULT NULL);
''')
db_conn.commit()
Expand All @@ -252,4 +239,5 @@ def reset(link_id):
db_conn.close()

app.run(host=conf.get('app', 'listen_addr'),
port=conf.getint('app', 'listen_port'), debug=True)
port=conf.getint('app', 'listen_port'),
debug=conf.getboolean('app', 'debug'))
2 changes: 1 addition & 1 deletion ldapass/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="static/js/bootstrap.min.js"></script>
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
</body>
</html>
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Flask
WTForms
python-ldap
sqlite3
WTForms
24 changes: 24 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python

from setuptools import setup, find_packages

setup(
name='ldapass',
version='0.1',
description='Web application for setting/changing LDAP user passwords.',
author='Bartek Rutkowski',
author_email='contact+ldapass@robakdesign.com',
license='BSD3',
url='https://github.com/bartekrutkowski/ldapass',
packages=find_packages(),
include_package_data=True,
package_data={
'ldapass': ['static/css/*',
'static/fonts/*',
'static/js/*',
'templates/*.html'
]
},
entry_points={'console_scripts': ['ldapass = ldapass:main']},
install_requires=['Flask', 'WTForms', 'python-ldap']
)
14 changes: 14 additions & 0 deletions tests/ldapass.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[app]
listen_addr = 0.0.0.0
listen_port = 80
smtp_addr = smtp-server.example.com
hostname = ldapass-hostname.example.com
database = ldapass.sql
ldap_debug = 0

[ldap]
addr = ldap-server.example.com
port = 389
user = cn=Manager,dc=example,dc=com
pass = very-complicated-ldap-password
basedn = ou=People,dc=example,dc=com
35 changes: 35 additions & 0 deletions tests/ldapass_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest

from ldapass import ldapass


@pytest.yield_fixture(autouse=True)
def flask_app():
ldapass.app.testing = True
ldapass.app.debug = True
with ldapass.app.app_context():
yield ldapass.app


@pytest.fixture
def test_client(flask_app):
return flask_app.test_client()


def test_get_response_code(test_client):
'''GET / reques should return 200 HTTP code.'''
resp = test_client.get('/')
assert resp.status_code == 200


def test_get_html_text(test_client):
'''GET / request should return html with proper text.'''
resp = test_client.get('/')
assert b'Setup/Reset LDAP Password' in resp.data


def test_get_html_form(test_client):
'''GET / request should return html with proper form.'''
resp = test_client.get('/')
assert b'<form role="form" method="post" action="/" class="form-horizontal">' in resp.data
assert b'<button class="btn btn-primary" type="submit">Submit</button>' in resp.data