Wednesday 19 October 2016

Web Based (Django) Password Change tool for Microsoft Active Directory

pyadselfservice (A short for Python Active Directory Self Service) is a software created using Python 3.10.12 & Django 5.0. The project aims to help IT Support teams in automating AD password change by providing a self service portal to end users. There are many commercial tools out there in the market, but this is a free alternative.

The tool authenticates users based on 2 factor authentication. The first factor being the secret and the second factor being OTP.

How it works:
The secrets is any information stored in the AD attributes and the second factor is One Time Password (OTP). After successful validation of First Factor, a OTP is sent to the Email address of the user.
Only after successful authentication against these 2 factors, a user can change his password.

For support and feedback on this software, please create a issue on GIT

Just to give you overview of this software before you begin with the installation, below are the few screenshots from the tool with successful deployment.

1. Home Page. 



2. First factor authentication using a secret stored in the AD attribute. Captcha is enabled for security.
In this example I have configured 'mobile' AD attribute to use user's mobile number as a secret. You can choose any other attribute that you deem fit and configure it in settings.py




3. Second factor authentication is based on OTP.



4. Below is a example email of the OTP. By default, the OTP is sent to the email address defined under "mail" AD attribute. If you have MS Exchange or any other email platform integrated with AD authentication then the email may not be accessible to user. In such implementations, you might want to send the OTP to users alternate email. You may need to repurpose any existing attribute to populate alternate email ID or add an additional attribute in the AD for storing alternate email. Once you decide on the right attribute to store alternate email, change PYADSELFSERVICE_ATTR2 in the settings.py

5. After successful 2 factor validation, the final page for entering new password.


6. After successful password change, below confirmation page will appear.



Installation & Configuration
Section 1:
The tool is tested in Ubuntu 22.04.3 LTS operating system with Python 3.10.12 & Django 5.0.  And MS Active Directory on Windows 2022 Server.

sudo apt-get update
sudo apt-get install apache2 libapache2-mod-wsgi-py3 python3-pip git
sudo pip install django ldap3 django-simple-captcha PyCrypto pyotp
sudo git clone https://github.com/kanayak123/pyadselfservice.git /opt/pyadselfservice

Section 2:

You will need to create a user account, with minimal access required to performing password reset and account unlock. After the user account is created, assign it with necessary permission required to perform password reset and account unlock. Below are the steps:
  • Open 'Active Directory Users and Computers'
  • Go to View - Click on "Advanced Features"
  • Right-click on Domain or Organization Unit that you want to grant this permission to.
  • Click the Security tab.
  • Click Advanced .
  • Click Add , and then click 'Select Principal' and specify the user account that you created above
  • Select "Type: Allow" and "Applies to: Descendant User objects"
  • In "Permissions", check 'Read all properties', 'Reset password', 'Read lockoutTime' and 'Write lockoutTime'. Click OK.





Important: MS Active Directory by design does not allow writes into any attributes unless the request is sent over LDAPs (636). You will need to enable SSL over LDAP on your domain controller if you haven't done it before. Please enable it referring this or you may use alternative methods.


Section 3:
Next, configure settings.py for authenticating Active Directory Domain Controller.

sudo vi /opt/pyadselfservice/pyadselfservice/settings.py

The configuration that you will need to edit as as below:
STATIC_URL = 'static/'
OTP_LOGIN_URL= '/' #Redirect page if OTP fails.

SMTP relay configuration for sending OTP emails. Note: If you are using gmail and have enabled 2FA, then please use the app password. Ref
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'urID@gmail.com'
EMAIL_HOST_PASSWORD = 'Your Password'
EMAIL_PORT = 587
DEFAULT_FROM_EMAIL="(Sender) Sender Name <urID@gmail.com>"
PYADSELFSERVICE_DCFQDN= #IP/FQDN of the Domain Controller
PYADSELFSERVICE_DCPORT = '636' #It should always be LDAPs.
PYADSELFSERVICE_DOMAINFQDN= #FQDN of the Active Directory Domain
PYADSELFSERVICE_USERNAME= #Username from section2 above
PYADSELFSERVICE_PASS= #Password for the same username
PYADSELFSERVICE_BASEDN= #Base DN of the domain.Ex: DC=exmaple,DC=local
PYADSELFSERVICE_ATTR2 = 'mail'
In this example, the email ID updated in 'mail' attribute will be used as recipient address for the OTP email trigger. If you have MS Exchange or any other email platform integrated with AD authentication, then you might want to send the OTP to users alternate email accessible to users such as thier Gmail or Yahoo. You may need to repurpose any existing attribute to populate alternate email ID or add an additional attribute in the AD for storing alternate email.

PYADSELFSERVICE_LOGPATH='/var/log/pyadselfservice/'
PYADSELFSERVICE_STOUT= #Session time-out in seconds. DO NOT include quotes.
PYADSELFSERVICE_STOUT= #Session time-out in seconds. DO NOT include quotes.
PYADSELFSERVICE_ATTR3 = #AD Attributes of your choice for validation ex: postalCode or mobile
Note: The information stored in this AD attribute is used for first factor authentication.


Please create the log path manually and assign necessary permissions. Run this command
sudo mkdir /var/log/pyadselfservice/ && sudo chown -R www-data:www-data /var/log/pyadselfservice/

Lastly, before you run the server, please run this command.
sudo python3 /opt/pyadselfservice/manage.py migrate


Section 4:
You may use the django run server command to run the start the server.
sudo python3 /opt/pyadselfservice/manage.py runserver 0.0.0.0:8000


Or 

You may configure Apache with mod-wsgi

You can also use Apache as web server. Please refer to Django documentation on How to use Django with Apache and mod_wsgi. However, you may use below configuration file as reference.
sudo vi /etc/apache2/sites-enabled/000-default.conf
WSGIScriptAlias / /opt/pyadselfservice/pyadselfservice/wsgi.py
WSGIPythonPath /opt/pyadselfservice/

<VirtualHost *:80>
        Alias /static/ /opt/pyadselfservice/pyadselfservice/static/
        <Directory /opt/pyadselfservice/pyadselfservice/static/>
            Require all granted
        </Directory>
        <Directory /opt/pyadselfservice/pyadselfservice/>
           <Files wsgi.py>
             Require all granted
           </Files>
        </Directory>
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

Restart Apache service and you are good to go. Also, always restart apache service after doing any changes in settings.py

You are done and good to go....

How to analyze the logs:
There are 2 log files in the path configured for logs i., debug.log and django_request.log.
The debug.log stores all LDAP transaction logs whereas django_request.log stores http exceptions.

During the password reset process in pyadselfservice, If you get a error message that says "Your password could not be changed. The password you entered  does not comply with the password policy. Please go back, enter a valid password and try again", then please go through debug.log.


1. The debug.log shows  2017-02-27 12:51:13,411 DEBUG log 7701 139735603336960 log PROTOCOL:MODIFY response <[{'description': 'unwillingToPerform', 'type': 'modifyResponse', 'referrals': None, 'dn': '', 'result': 53, 'message': '0000052D: SvcErr: DSID-031A12D2, problem 5003 (WILL_NOT_PERFORM), data 0\n\x00'}]> received via <ldaps://10.xx.xx.xxx:636 - ssl - user: prevuser@domain.local - not lazy - bound - open - <local: 10xx.xx.xx:44486 - remote: 10.xxx.xx.xx:636> - tls not started - listening - SyncStrategy - internal decoder>
The error 'unwillingtoperform' appears if a user tried to submit a password which does not comply with the password policy applied on your AD through Group Policy Object.
Or if the user account for which the password being changed has more privileges than the account configured in section 4 above Ex:- when you try to change password for an administrator account with this tool but the user account configured in Section 4 above does not have administrator privileges. This behavior is by Active Directory design meant for better security.

2. The debug.logs show 2017-02-27 12:50:11,321 DEBUG log 7701 139735720654592 log PROTOCOL:MODIFY response <[{'description': 'constraintViolation', 'type': 'modifyResponse', 'referrals': None, 'dn': '', 'result': 19, 'message': '0000052D: AtrErr: DSID-03191083, #1:\n\t0: 0000052D: DSID-03191083, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 9005a (unicodePwd)\n\x00'}]> received via <ldaps://10.xxx.xx.xx:636 - ssl - user: username@domain.local - not lazy - bound - open - <local: 10xx.xx.xx:44464 - remote: 10.xxx.xx.xx:636> - tls not started - listening - SyncStrategy - internal decoder>
The error 'constraintViolation' appears if a user tries to reuse his existing password and the "Enforce password history" under Password Policy in the AD Group Policy is configured to remember previous passwords. This behavior is by AD design meant for security.