LDAP mocking in Python (ldap3 module) - python

My current task is to do QA Automation on our web app, but I don't want to use real credentials for it (for which we use a LDAP server). My idea was to mock the LDAP server when the web app is in TEST_MODE, and to my luck I found out that 'ldap3' (the python module) that we use for authentication, also supports a mocking function. An example code is here:
from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, MOCK_SYNC
REAL_SERVER = 'my_real_server'
REAL_USER = 'cn=my_real_user,ou=test,o=lab'
REAL_PASSWORD = 'my_real_password'
# Retrieve server info and schema from a real server
server = Server(REAL_SERVER, get_info=ALL)
connection = Connection(server, REAL_USER, REAL_PASSWORD, auto_bind=True)
# Store server info and schema to json files
server.info.to_file('my_real_server_info.json')
server.schema.to_file('my_real_server_schema.json')
# Read entries from a portion of the DIT from real server and store them in a json file
if connection.search('ou=test,o=lab', '(objectclass=*)', attributes=ALL_ATTRIBUTES):
connection.response_to_file('my_real_server_entries.json', raw=True)
# Close the connection to the real server
connection.unbind()
# Create a fake server from the info and schema json files
fake_server = Server.from_definition('my_fake_server', 'my_real_server_info.json', 'my_real_server_schema.json')
# Create a MockSyncStrategy connection to the fake server
fake_connection = Connection(fake_server, user='cn=my_user,ou=test,o=lab', password='my_password', client_strategy=MOCK_SYNC)
# Populate the DIT of the fake server
fake_connection.strategy.entries_from_json('my_real_server_entries.json')
# Add a fake user for Simple binding
fake_connection.strategy.add_entry('cn=my_user,ou=test,o=lab', {'userPassword': 'my_password', 'sn': 'user_sn', 'revision': 0})
# Bind to the fake server
fake_connection.bind()
I followed this example with our code, and was successful halfway through it. I extracted the real_server_entries in a json file, and now is part to do the fake connection. So to summarise everything is done until this part:
# Create a MockSyncStrategy connection to the fake server
fake_connection = Connection(fake_server, user='cn=my_user,ou=test,o=lab', password='my_password', client_strategy=MOCK_SYNC)
I am not really sure what to put in the place of user and password.
A part of my code:
_USER_SEARCH_FILTER = "(&(objectClass=user)(cn={}))"
_ALL_USERS_SEARCH_FILTER = "(&(objectCategory=person)(objectClass=user))"
_EMAIL_ATTRIBUTE = "mail"
_DISPLAY_NAME_ATTRIBUTE = "displayName"
_USERNAME_ATTRIBUTE = "cn"
_SEARCH_ATTRIBUTES = (
_EMAIL_ATTRIBUTE, _DISPLAY_NAME_ATTRIBUTE, _USERNAME_ATTRIBUTE)
_LDAP_CONNECTION_ERROR = "Connection to LDAP server %s:%s failed: %s"
_LDAP_SERVER = Server(host=LDAP.host, port=int(LDAP.port), get_info='ALL')
server = _LDAP_SERVER
_CONNECTION = Connection(
server,
LDAP.manager_dn, LDAP.manager_password,
auto_bind=True, client_strategy=RESTARTABLE
)
server.info.to_file('my_real_server_info.json')
server.schema.to_file('my_real_server_schema.json')
if _CONNECTION.search(LDAP.root_dn, _ALL_USERS_SEARCH_FILTER, attributes=_SEARCH_ATTRIBUTES):
_CONNECTION.response_to_file('my_real_server_entries.json', raw=True)
_CONNECTION.unbind()
mock_server = Server.from_definition('mock_server', 'my_real_server_info.json', 'my_real_server_schema.json')
mock_connection = Connection(mock_server, user='???', password='???', client_strategy=MOCK_SYNC)
mock_connection.strategy.entries_from_json('my_real_server_entries.json')
mock_connection.strategy.add_entry('LDAP.root_dn', { #My guess is that here I mock the attributes, but this is also the other problem I am having (check below) })
The other problem I have is that I am not even sure if I can add a fake user, because when I took a look into the real_entries.json file the password is not stored there as an attribute (not even an encrypted version of it) and the only attributes we have are:
`cn` - username
`displayName` - LASTNAME, FIRSTNAME
`mail` - example#mail.com

Related

python aiosmtpd server with basic authentication

Im trying to create an aiosmtpd server to process emails received.
It works great without authentication, yet i simply cannot figure out how to setup the authentication.
I have gone through the documents and searched for examples on this.
a sample of how im currently using it:
from aiosmtpd.controller import Controller
class CustomHandler:
async def handle_DATA(self, server, session, envelope):
peer = session.peer
mail_from = envelope.mail_from
rcpt_tos = envelope.rcpt_tos
data = envelope.content # type: bytes
# Process message data...
print('peer:' + str(peer))
print('mail_from:' + str(mail_from))
print('rcpt_tos:' + str(rcpt_tos))
print('data:' + str(data))
return '250 OK'
if __name__ == '__main__':
handler = CustomHandler()
controller = Controller(handler, hostname='192.168.8.125', port=10025)
# Run the event loop in a separate thread.
controller.start()
# Wait for the user to press Return.
input('SMTP server running. Press Return to stop server and exit.')
controller.stop()```
which is the basic method from the documentation.
could someone please provide me with an example as to how to do simple authentication?
Alright, since you're using version 1.3.0, you can follow the documentation for Authentication.
A quick way to start is to create an "authenticator function" (can be a method in your handler class, can be standalone) that follows the Authenticator Callback guidelines.
A simple example:
from aiosmtpd.smtp import AuthResult, LoginPassword
auth_db = {
b"user1": b"password1",
b"user2": b"password2",
b"user3": b"password3",
}
# Name can actually be anything
def authenticator_func(server, session, envelope, mechanism, auth_data):
# For this simple example, we'll ignore other parameters
assert isinstance(auth_data, LoginPassword)
username = auth_data.login
password = auth_data.password
# If we're using a set containing tuples of (username, password),
# we can simply use `auth_data in auth_set`.
# Or you can get fancy and use a full-fledged database to perform
# a query :-)
if auth_db.get(username) == password:
return AuthResult(success=True)
else:
return AuthResult(success=False, handled=False)
Then you're creating the controller, create it like so:
controller = Controller(
handler,
hostname='192.168.8.125',
port=10025,
authenticator=authenticator_func, # i.e., the name of your authenticator function
auth_required=True, # Depending on your needs
)

Error - UNWILLING_TO_PERFORM - while change user password in AD ldap using python code

I am creating a simple python function to change the user password. I have tested my AD set up, able to search the user and get correct response but when try to run l.modify_s, I get the below error. AD user has the required permissions. Not sure why am I getting this error.
Any help will be great. Please let me know if you need any more information or code as well to understand the issue better.
"errorType": "**UNWILLING_TO_PERFORM**",
"errorMessage": "{'info': u'0000001F: SvcErr: DSID-031A12D2, problem 5003 (WILL_NOT_PERFORM), data 0\\n', 'msgid': 3, 'msgtype': 103, 'result': 53, 'desc': u'Server is unwilling to perform', 'ctrls': []}"
}```
Please find my code below
``` import ldap
import os
import boto3
import random
import string
from base64 import b64decode
import ldap
def lambda_handler(event, context):
try:
cert = os.path.join('/Users/marsh79/Downloads', 'Serverssl.cer')
print "My cert is", cert
# LDAP connection initialization
l = ldap.initialize('ldap://example.corp.com')
# Set LDAP protocol version used
l.protocol_version = ldap.VERSION3
#Force cert validation
l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
# Set path name of file containing all trusted CA certificates
l.set_option(ldap.OPT_X_TLS_CACERTFILE, cert)
# Force libldap to create a new SSL context (must be last TLS option!)
l.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
bind = l.simple_bind_s("admin#corp.example.com", "secret_pass")
base = "OU=Enterprise,OU=Users,OU=corp,DC=corp,DC=example,DC=com"
criteria = "(objectClass=user)"
attributes = ['distinguishedName']
result = l.search_s(base, ldap.SCOPE_SUBTREE, criteria, attributes)
results = [entry for dn, entry in result if isinstance(entry, dict)]
new_password='secretpass_new'
unicode_pass = unicode('\"' + new_password + '\"', 'iso-8859-1')
password_value = unicode_pass.encode('utf-16-le')
add_pass = [(ldap.MOD_REPLACE, 'unicodePwd', [password_value])]
print "My result distinguishedName1:", results[0]['distinguishedName'][0]
print "My result distinguishedName2:", results[1]['distinguishedName'][0]
l.modify_s(results[0]['distinguishedName'][0],add_pass)
print results
finally:
l.unbind()
I have checked multiple things
Password complexity is good
Enabled secured ldap on my AD server and tested this using ldp.exe and I can connect using port 636
I am able to run this code if I just need to search the user. I get the search results.
But when I try to modify the password, it breaks and my head is just throwing up to work out where it is going wrong :X
I'm not a Python programmer, but I know how AD and LDAP works. It's probably still not connected via LDAPS. From examples I've seen online, you might need to specify ldaps://:
l = ldap.initialize('ldaps://<server name>.corp.example.com')
Or possibly the port as well:
l = ldap.initialize('ldaps://<server name>.corp.example.com:636')
You don't need to supply the cert file on the client side, but the issuer of the certificate on the server must be trusted by the client computer. I guess that's what you're trying to do with cert. But you may not have to. Try without that and see what happens. If you're running this on Windows, it may use the Trusted Certificate Store from Windows itself and it should work as long as the server isn't using a self-signed cert.

Mocking a LDAP Server in Python (ldap3)

I am currently trying to mock the LDAP server we are using in the company. For connections to it in our web app we use python ldap3, so I decided to use the mocking feature of ldap3 (which is documented here: https://ldap3.readthedocs.io/mocking.html). However, it can't seem to work for some reason and the resources online on this topic are scarce.
This is my current code:
//imports and environment variables for the LDAP servers
...
_LDAP_SERVER = Server(host=LDAP.host, port=int(LDAP.port), get_info='ALL')
server = _LDAP_SERVER
#connection to real server
_CONNECTION = Connection(
server,
LDAP.manager_dn, LDAP.manager_password,
auto_bind=True, client_strategy=RESTARTABLE
)
#extracting the json files
server.info.to_file('my_real_server_info.json')
server.schema.to_file('my_real_server_schema.json')
#getting the real server entries - everything works to this point
if _CONNECTION.search(LDAP.root_dn, _ALL_USERS_SEARCH_FILTER, attributes=_SEARCH_ATTRIBUTES):
_CONNECTION.response_to_file('my_real_server_entries.json', raw=True)
_CONNECTION.unbind()
#creating the mock server per guidelines
mock_server = Server.from_definition('Mock Server', 'my_real_server_info.json', 'my_real_server_schema.json')
#making a new fake connection
fake_connection = Connection(mock_server, user='CN=testuser, CN=users, DC=company, DC=com', password='fakepassword',
client_strategy=MOCK_SYNC)
fake_connection.strategy.add_entry('CN=selen001,CN=users, DC=company,DC=com', {
"cn": "selen001", #username
"displayName": "Admin, selenium",
"mail": "selenium#COMPANY.COM",
}
)
fake_connection.strategy.add_entry('CN=selen002,CN=users,DC=company,DC=int', {
"cn": "selen002", #username
"displayName": "User, selenium",
"mail": "selenium2#COMPANY.COM",
}
)
fake_connection.bind()
#I want to test if it works, but I can't get any results
if fake_connection.search('DC=company,DC=com', _ALL_USERS_SEARCH_FILTER, attributes=_SEARCH_ATTRIBUTES):
fake_connection.response_to_file('my_real_server_entries1337.json', raw=True)
So to summarise: (1) Connection to Real Server, (2) get its schema and info, (3) generate its entities, (4) create a mock server and a fake connection with fake user, (5) add fake users, (6) test if it works (I can't get a result out of this, which leads me to think that there is an error somewhere, even though I followed the code closely..).
Thank you.

Django app SSL socket connection to firmware

I have a Django app created using Django rest framework. Below is the configuration that my setup is using:
Django 1.9
mongoDB as backend
gunicorn
nginx
Now I have created an API to enter data in DB and retrieve it using REST. I have test it via postman and it is working fine. We have a firmware which is consuming those APIs and that team want to use SSL socket connection instead of REST API.
I am new in SSL Socket connection and I am not able to find anything on internet that can help me on this. I know it is possible to create socket in Python but I am not able to understand how to use it in Django app to Read/Write data in mongoDB.
Any guidance will be very helpful.
TO READER : I understand you may want to close this question but please put up a remark on how to get guidance on this as SO is the biggest portal for putting up questions.
EDIT 1 : I am adding the code of my serializer.py API.
from rest_framework import serializers
class SaveUserLogs(serializers.Serializer):
token = serializers.CharField(label=_("Token"))
device_ip = serializers.CharField(label=_('Device IP'))
device_name = serializers.CharField(label=_('Device Name'))
device_os = serializers.CharField(label=_('Device OS'))
device_macid = serializers.CharField(label=_('Device MAC ID'))
dest_port = serializers.CharField(label=_('Device Port'))
conn_type = serializers.CharField(label=_('Connection Type'))
date = serializers.CharField(label=_('modified date'))
def validate(self, attrs):
device_macid = attrs.get('device_macid')
token = attrs.get('token')
if device_macid:
tokendetails = validate_token(token)
if not tokendetails:
msg = _('Invalid token.')
raise serializers.ValidationError(msg)
else:
userdetails = tokendetails.user
if userdetails.check_mac_id(device_macid):
all_logs = UserLog.objects.all().order_by("-id")
log_id = 1
if all_logs:
getid = all_logs[0].id
log_id = getid + 1
new_log = UserLog(
id=log_id,
device_ip=attrs.get('device_ip'),
device_name=attrs.get('device_name'),
device_os=attrs.get('device_os'),
device_macid=attrs.get('device_macid').upper(),
dest_port=attrs.get('dest_port'),
conn_type=attrs.get('conn_type'),
)
new_log.save()
print("saving log", log_id)
else:
msg = _('Invalid MAC ID')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "MAC ID".')
raise serializers.ValidationError(msg)
attrs['mac_id'] = userdetails.mac_id
return attrs
It looks like you require socket library provided with python. But using this is not recommended since you will have to take care of lot of low level networking stuff and your program will get complicated.
You should instead keep your REST api and run your nginx server on https. You can then write the code on firmware to send and receive data from the server using https.
If for some reason you don't want to use https then you should use requests library to write your server.

mySQL UPDATE doesn't work for GAE when called from browser

I am writing a verify email address python file for Google App Engine. (ya I know django has stuff, but I wanted to write my own because that is how I learn)
Below is the python code. The code returns "Email Account Verified" which seems to me that the queries worked. However when I look at the "active" column in the database, it is still 0.
If I run the query string that logging.info("%s",db_query) in the database itself, it works and is updated to 1.
All my other python code (with UPDATES) works fine, the only difference is those python files are called from my ios app and this one is called from a browser.
#Make the libs folder with 3rd party libraries and common methods
import sys
sys.path.insert(0, 'libs')
#Imports
import logging
import webapp2
from django.utils.html import strip_tags
import common
import MySQLdb
import json
VERIFIED_HTML = """\
<html>
<body>
<h1>Email Account Verified</h1>
</body>
</html>
"""
ERROR_HTML = """\
<html>
<body>
<h1>ERROR</h1>
</body>
</html>
"""
class VerifyEmail(webapp2.RequestHandler):
def get(self):
user_email = strip_tags(self.request.get('user_email').lower().strip())
user_activation_hash = strip_tags(self.request.get('user_activation_hash').strip())
logging.info("User Email = %s", user_email)
logging.info("User Activation Hash = %s", user_activation_hash)
#Insert the information into the users table
#Get the database connection to Google Cloud SQL
db = common.connect_to_google_cloud_sql()
db_cursor = db.cursor(MySQLdb.cursors.DictCursor)
#Check to see if user already exists
#Query for user
db_query = """SELECT \
email, activation_hash \
FROM users WHERE email='%s' AND activation_hash='%s'""" % (user_email, user_activation_hash)
db_cursor.execute(db_query)
#If there is one record containing the username check password
if(db_cursor.rowcount == 1):
db_query = """UPDATE users SET active=%s WHERE email='%s';""" % (1, user_email)
logging.info("%s" % db_query)
if(db_cursor.execute(db_query)):
self.response.write(VERIFIED_HTML)
else:
self.response.write(ERROR_HTML)
else: #either no user, or activation_hash doesn't match
self.response.write(ERROR_HTML)
Connect to Google Cloud SQL
def connect_to_google_cloud_sql():
#hostname = DEV_DB_HOSTNAME
#hostname = PROD_DB_HOSTNAME
db_username = 'dummy_user' #not real
db_password = 'dummypassword' # not real
#If PROD or Deployed Testing, use unix_socket
if(os.getenv('SERVER_SOFTWARE') and os.getenv('SERVER_SOFTWARE').startswith('Google App Engine/')):
db = MySQLdb.connect(unix_socket='/cloudsql/' + _DATABASE_HOSTNAME, db='dummydbname', user=db_username, passwd=db_password)
else: #Local Testing uses host
db = MySQLdb.connect(host=_DATABASE_HOSTNAME, port=3306, db='dummydbname', user=db_username, passwd=db_password)
logging.info("Got DB Connection")
return db
Any suggestions? Is it GAE Cloud SQL Privledges????
Maybe because I was using my browser with the local app engine running on my local ip???
You need to call .commit() on MySQLdb cursors after executing queries. This is why your update is failing. It updates inside the transaction, but when your code ends without committing the transaction, the changes to the DB are rolled back, despite having told the user of success on update.
You can also use the following method to ensure commits when using a cursor: db_cursor.autocommit(True).

Categories

Resources