implement authentication in request send to LDAP server for connecting in python3 - python-3.x

I have used Django in python3 to develop website.
Now I want to implement LDAP login.
In our LDAP server, a fixed authentication string should be send in the request to LDAP server.
That is, in headers of the request, authentication item should be: "example_auth"
However, in connection class offered by ldap3 package, authentication could only be set to SIMPLE, ANONYMOUS, SASL, NTLM.
Then how could I set authentication to my authentication code for LDAP login?
class LDAPBackend:
def authenticate(self, request, username=None, password=None, **kwargs):
# set username to lowercase for consistency
username = username.lower()
# get the bind client to resolve DN
logger.info('authenticating %s' % username)
# set your server
server = Server("example.com/REST/json/service/loginStaff", port=389)
try:
conn = Connection(server, user=username, password=password, auto_bind=True,)
conn.open()
conn.bind()
except LDAPBindError as e:
logger.info('LDAP authentication failed')
logger.info(e)
return None
user = UserModel.objects.update_or_create(username=username)
return user
def get_user(self, user_id):
try:
return UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None

For anyone else who finds this page, this worked for me but I had to combine the above with elements from the source code that the author mentions. To make someone else's life easier, here's the complete config I needed to replace the authenticate method with LDAP:
# Add to settings.py:
... <YOUR OTHER SETTINGS ABOVE>
LDAP_HOST = '<YOUR LDAP HOST>'
LDAP_DOMAIN = '<YOUR DOMAIN'
LDAP_BASE_DN = 'OU=<YOU OU>,dc=< YOUR DC...>'
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# Age of cookie, in seconds (default: 2 weeks, here set to 26 weeks).
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 26
AUTHENTICATION_BACKENDS = [
'django_react.backends.LDAPBackend',
'django.contrib.auth.backends.ModelBackend'
]
---- end config file
And this:
# add to django_react/backends.py:
---- start config file
import logging
from ldap3 import Server, Connection, SAFE_SYNC, SUBTREE
from ldap3.core.exceptions import *
logger = logging.getLogger(__name__)
from django.conf import settings
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class LDAPBackend:
def get_user(self, user_id):
try:
return UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None
def authenticate(self, request, username=None, password=None, **kwargs):
# set username to lowercase for consistency
username = username.lower()
# get the bind client to resolve DN
logger.info('authenticating %s' % username)
# set your server
server = Server(settings.LDAP_HOST, get_info='ALL')
try:
conn = Connection(server, f"{username}#{settings.LDAP_DOMAIN}", password=password, client_strategy=SAFE_SYNC,
auto_bind=True)
status, result, response, _ = conn.search(
search_base=settings.LDAP_BASE_DN,
search_filter='(&(objectClass=person)(samaccountname=' + username + '))',
search_scope=SUBTREE,
attributes=['samaccountname', 'givenName', 'mail', 'sn']
)
except LDAPBindError as e:
logger.info('LDAP authentication failed')
logger.info(e)
return None
user, created = UserModel.objects.update_or_create(username=username,
defaults={
'first_name': response[0]['attributes']['givenName'],
'last_name': response[0]['attributes']['sn'],
'email': response[0]['attributes']['mail']
}
)
return user
---- end config file

You need to change the "Server". LDAP does not support URLs and as far as I know, it does not support headers as it is a different protocol than http/s
Set:
settings.LDAP_HOST to your LDAP server IP or FQDN (e.g. 192.168.x.x or pdc.ad.example.com)
settings.LDAP_DOMAIN to you LDAP Domain (e.g. ad.example.com)
settings.LDAP_BASE_DN to the Organization Unit(OU) under which your users exist. (e.g. OU=Users,dc=ad,dc=example,dc=com)
Below code will authenticate users against LDAP and Create or Update user in Django on successful authentication.
Tested on MS Active Directory via LDAP.
Original code taken from https://stackoverflow.com/a/63510132
from ldap3 import Server, Connection, SAFE_SYNC, SUBTREE
from ldap3.core.exceptions import *
def authenticate(self, request, username=None, password=None, **kwargs):
# set username to lowercase for consistency
username = username.lower()
# get the bind client to resolve DN
logger.info('authenticating %s' % username)
# set your server
server = Server(settings.LDAP_HOST, get_info='ALL')
try:
conn = Connection(server, f"{username}#{settings.LDAP_DOMAIN}", password=password, client_strategy=SAFE_SYNC, auto_bind=True)
status, result, response, _ = conn.search(
search_base = settings.LDAP_BASE_DN,
search_filter = '(&(objectClass=person)(samaccountname='+username+'))',
search_scope = SUBTREE,
attributes = ['samaccountname', 'givenName', 'mail', 'sn']
)
except LDAPBindError as e:
logger.info('LDAP authentication failed')
logger.info(e)
return None
user, created = UserModel.objects.update_or_create(username=username,
defaults={
'first_name': response[0]['attributes']['givenName'],
'last_name':response[0]['attributes']['sn'],
'email':response[0]['attributes']['mail']
}
)
return user

Related

Lambda code getting error while accessing secrets manager

I'm pretty new to lambda and python. i got a readymade code from google for an activity by creating Username and Password based SFTP with Lambda as IdP in AWS Transfer Family.
I have no clue which field i need to add the values of server id, user, password, region, etc... I'm typically cloud admin. This is the cloudwatch error logs getting from this.
ERRORS AUTH_FAILURE Method=password User=new Message="{"errorMessage": "'s-2eccd17fcd5244f68'", "errorType": "KeyError", "requestId": "61a86f3d-7a5b-4763-97e9-f97b74c77d58", "stackTrace": [" File \"/var/task/lambda_function.py\", line 22, in lambda_handler\n input_serverId = event[\"s-2eccd17fcd5244f68\"]\n"]}" SourceIP=x.x.x.x
Lambda unable to fetch the password from secrets manager it looks like. i have given sufficient IAM permission for this
Here is the lambda code..
import os
import json
import boto3
import base64
from ipaddress import ip_network, ip_address
from botocore.exceptions import ClientError
def lambda_handler(event, context):
# Get the required parameters
required_param_list = ["serverId", "username", "protocol", "sourceIp"]
for parameter in required_param_list:
if parameter not in event:
print("Incoming " + parameter + " missing - Unexpected")
return {}
input_serverId = event["serverId"]
input_username = event["username"]
input_protocol = event["protocol"]
input_sourceIp = event["sourceIp"]
input_password = event.get("password", "")
print("ServerId: {}, Username: {}, Protocol: {}, SourceIp: {}"
.format(input_serverId, input_username, input_protocol, input_sourceIp))
# Check for password and set authentication type appropriately. No password means SSH auth
print("Start User Authentication Flow")
if input_password != "":
print("Using PASSWORD authentication")
authentication_type = "PASSWORD"
else:
if input_protocol == 'FTP' or input_protocol == 'FTPS':
print("Empty password not allowed for FTP/S")
return {}
print("Using SSH authentication")
authentication_type = "SSH"
# Retrieve our user details from the secret. For all key-value pairs stored in SecretManager,
# checking the protocol-specified secret first, then use generic ones.
# e.g. If SFTPPassword and Password both exists, will be using SFTPPassword for authentication
secret = get_secret(input_serverId + "/" + input_username)
if secret is not None:
secret_dict = json.loads(secret)
# Run our password checks
user_authenticated = authenticate_user(authentication_type, secret_dict, input_password, input_protocol)
# Run sourceIp checks
ip_match = check_ipaddress(secret_dict, input_sourceIp, input_protocol)
if user_authenticated and ip_match:
print("User authenticated, calling build_response with: " + authentication_type)
return build_response(secret_dict, authentication_type, input_protocol)
else:
print("User failed authentication return empty response")
return {}
else:
# Otherwise something went wrong. Most likely the object name is not there
print("Secrets Manager exception thrown - Returning empty response")
# Return an empty data response meaning the user was not authenticated
return {}
def lookup(secret_dict, key, input_protocol):
if input_protocol + key in secret_dict:
print("Found protocol-specified {}".format(key))
return secret_dict[input_protocol + key]
else:
return secret_dict.get(key, None)
def check_ipaddress(secret_dict, input_sourceIp, input_protocol):
accepted_ip_network = lookup(secret_dict, "AcceptedIpNetwork", input_protocol)
if not accepted_ip_network:
# No IP provided so skip checks
print("No IP range provided - Skip IP check")
return True
net = ip_network(accepted_ip_network)
if ip_address(input_sourceIp) in net:
print("Source IP address match")
return True
else:
print("Source IP address not in range")
return False
def authenticate_user(auth_type, secret_dict, input_password, input_protocol):
# Function returns True if: auth_type is password and passwords match or auth_type is SSH. Otherwise returns False
if auth_type == "SSH":
# Place for additional checks in future
print("Skip password check as SSH login request")
return True
# auth_type could only be SSH or PASSWORD
else:
# Retrieve the password from the secret if exists
password = lookup(secret_dict, "Password", input_protocol)
if not password:
print("Unable to authenticate user - No field match in Secret for password")
return False
if input_password == password:
return True
else:
print("Unable to authenticate user - Incoming password does not match stored")
return False
# Build out our response data for an authenticated response
def build_response(secret_dict, auth_type, input_protocol):
response_data = {}
# Check for each key value pair. These are required so set to empty string if missing
role = lookup(secret_dict, "Role", input_protocol)
if role:
response_data["Role"] = role
else:
print("No field match for role - Set empty string in response")
response_data["Role"] = ""
# These are optional so ignore if not present
policy = lookup(secret_dict, "Policy", input_protocol)
if policy:
response_data["Policy"] = policy
# External Auth providers support chroot and virtual folder assignments so we'll check for that
home_directory_details = lookup(secret_dict, "HomeDirectoryDetails", input_protocol)
if home_directory_details:
print("HomeDirectoryDetails found - Applying setting for virtual folders - "
"Note: Cannot be used in conjunction with key: HomeDirectory")
response_data["HomeDirectoryDetails"] = home_directory_details
# If we have a virtual folder setup then we also need to set HomeDirectoryType to "Logical"
print("Setting HomeDirectoryType to LOGICAL")
response_data["HomeDirectoryType"] = "LOGICAL"
# Note that HomeDirectory and HomeDirectoryDetails / Logical mode
# can't be used together but we're not checking for this
home_directory = lookup(secret_dict, "HomeDirectory", input_protocol)
if home_directory:
print("HomeDirectory found - Note: Cannot be used in conjunction with key: HomeDirectoryDetails")
response_data["HomeDirectory"] = home_directory
if auth_type == "SSH":
public_key = lookup(secret_dict, "PublicKey", input_protocol)
if public_key:
response_data["PublicKeys"] = [public_key]
else:
# SSH Auth Flow - We don't have keys so we can't help
print("Unable to authenticate user - No public keys found")
return {}
return response_data
def get_secret(id):
region = os.environ["SecretsManagerRegion"]
print("Secrets Manager Region: " + region)
print("Secret Name: " + id)
# Create a Secrets Manager client
client = boto3.session.Session().client(service_name="secretsmanager", region_name=region)
try:
resp = client.get_secret_value(SecretId=id)
# Decrypts secret using the associated KMS CMK.
# Depending on whether the secret is a string or binary, one of these fields will be populated.
if "SecretString" in resp:
print("Found Secret String")
return resp["SecretString"]
else:
print("Found Binary Secret")
return base64.b64decode(resp["SecretBinary"])
except ClientError as err:
print("Error Talking to SecretsManager: " + err.response["Error"]["Code"] + ", Message: " +
err.response["Error"]["Message"])
return None
`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
If you want to get Python code working with Secrets Manager, I recommend getting this Secret Manager Python code working from an IDE. Make sure you can get it working and you understand it.
https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/python/example_code/secretsmanager/secretsmanager_basics.py
Once you understand that, you can then move to building a Lambda function using this Python code. When you build a Lambda function, you need to make sure that the IAM role you run the Lambda with has permissions to invoke Secret Manager.

Keycloak API :- Identify Users Realm for login

I am creating a microservice that will proxy keycloak for user creation, reset password, login etc. I don't want to expose any keycloak page like re-set password or login page so I am using the keycloak API and everything is fine so far.
The only issue is for login, where I need to know the realm to get the token as the API to get the token is realm specific.
realms/{REALM_NAME}/protocol/openid-connect/token
So is there a way to get a list of all the users from all the realms by admin user?
or any other way to find it?
You can get the realm information by decode user's access token.
The "iss" (issuer) claim identifies the principal that issued the JWT.
This is decode example by JWT.io
I demo make two realms (realm1 and realm2)
each realm add single user (both user same username: user and password: 1234)
And call get access token and decode it by Python
import requests
import ast
import jwt
def get_issuer(realm, user_name, password):
url = 'http://localhost:8180/auth/realms/'+realm+'/protocol/openid-connect/token'
body = {
'client_id': 'admin-cli',
'grant_type': 'password',
'username' : user_name,
'password': password
}
headers = {
'content-type': 'application/x-www-form-urlencoded'
}
response = requests.post(url, data=body, headers=headers).content.decode('utf-8')
token = ast.literal_eval(response)['access_token']
# print(token)
decoded = jwt.decode(token, options={"verify_signature": False})
# print(decoded)
return decoded['iss']
print('realm1 with user -->', get_issuer('realm1','user','1234'))
print('realm2 with user -->', get_issuer('realm2','user','1234'))
get this output
$python get_realm.py
realm1 with user --> http://localhost:8180/auth/realms/realm1
realm2 with user --> http://localhost:8180/auth/realms/realm2
If you want to get all users of realm,
you can get this API with master realm's admin token
GET /{realm}/users
After talking with No_One, I realize he want to get a relam name by username.
I made 600 realms and 3 users for each realm. So total user 1.8K and each user unique username. (If may same username, we can extend username and e-mail). I made python program to create relams and users.
So I demo search realm by username with for loop.
Check username exist for every realm
For loop all of relams
{keycloak_URL}/auth/admin/realms/{realm_name}/users/?username={user_name}
if you want to get list of realms,
{keycloak-url}/auth/admin/realms
The realm name format is realm_0xxx
example) realm_0001, realm_0002, ..., realm_0600
each ream has three users
example) In realm_0001,
user01_in_realm0001,
user02_in_realm0001,
user03_in_realm0001
In realm_0002,
user01_in_realm0002,
user02_in_realm0002,
user03_in_realm0002
...
In realm_0600,
user01_in_realm0600,
user02_in_realm0600,
user03_in_realm0600
This Python code search user by for loop
import admin
import random
admin = admin.Admin()
token = admin.get_master_token()
random_realm_num = random.randint(1, 600)
random_user_num = random.randint(1, 3)
realm_name = "realm_{:04d}".format(random_realm_num)
user_name = "user{:02d}_in_realm{:04d}".format(random_user_num, random_realm_num)
print('random realm_name:', realm_name)
print('random user_name:', user_name)
found = False
for realm_index in range(1,600,1):
realm_name = "realm_{:04d}".format(realm_index)
if(admin.is_user_exist(token, realm_name, user_name)):
print('user_name:', user_name,' belong to',realm_name)
found = True
break
if (not found):
print('user_name:', user_name,'is not belong to any realms')
This admin class
from urllib import response
from urllib.error import HTTPError
import requests
import ast
import json
class Admin:
# Keycloak master realm URL
url = 'http://localhost:8180/auth/realms/master/protocol/openid-connect/token'
# Keycloak master credential
params = {
'client_id': 'admin-cli',
'grant_type': 'password',
'username' : 'admin',
'password': 'admin'
}
def get_master_token(self):
try:
response = requests.post(self.url, self.params, verify=False).content.decode('utf-8')
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
print('Keycloak container is not running, Please check your docker container!')
raise SystemExit
else:
return ast.literal_eval(response)['access_token']
def is_user_exist(self, token, realm_name, user_name):
url ='http://localhost:8180/auth/admin/realms/'+realm_name+'/users/?username='+user_name.replace(" ", "%20")
headers = {
'content-type': 'application/json',
'Authorization' : 'Bearer '+ str(token)
}
try:
response = requests.get(url, headers=headers)
# print (response)
# print (response.content)
# If the response was successful, no Exception will be raised
response.raise_for_status()
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
else:
# print('Success!')
# print(response.text)
if len(response.content) == 2: # []
return False
if (json.loads(response.text)[0]['username'] == user_name.lower()):
return True
else:
return False
Result
random realm_name: realm_0430
random user_name: user03_in_realm0430
user_name: user03_in_realm0430 belong to realm_0430
[Done] exited with code=0 in 21.248 seconds
We also encountered such problem but we finally implemented by this way:
create a new SPI and provide a new rest endpoint like '/realm-list'
it will return a list of realms that doesn't require admin privilege to access
provide a page to list and choose your realm and than click a button
forward current page to login page(the realm will reprenset in url path)
one thing needs to note, the backend of keycloak needs to check if user is logged in, we add a new cookie value to mark whether the user is logged in.

How to redirect a page from one blueprint view to another?

I am using Flask along with the Blueprint module. In my application, I am trying to redirect the page to page/home after a successful login (user/login) using LDAP but the redirect takes forever without throwing any error.
I tried a couple of different variations of redirect(url_for('page.home')), redirect(url_for('page/home.html')). But each of these commands do not redirect. I am not sure what I am doing wrong. Kindly help.
Folder structure:
user/views.py:
from flask import Flask, Blueprint, request, render_template, redirect, url_for, session
from ldap3 import Server, Connection, ALL, NTLM
from snakeeyes.blueprints.page.views import page
import config.settings as p
user = Blueprint('user', __name__, template_folder='templates')
user.secret_key = 'dev key'
# #user.route('/')
# #user.route('/login')
# def login():
# return render_template('user/login.html')
def connect_ldap(username, password):
if not username or not password:
return False
# try:
# from ldap3 import Server, Connection, ALL, NTLM
# except ImportError as importException:
# print("LDAP3 import not found, run 'sudo pip install ldap3 && sudo pip3 install ldap3'")
# print(importException)
# return False
# define the server
server = Server('us01ds', port=389, get_info=ALL)
# define the connection
user = 'uid=%s,ou=people,ou=users,dc=global,dc=COMPANY,dc=com' % username
conn = Connection(server, user, password, auto_bind=True)
# perform the Bind operation
if not conn.bind():
print('error in bind', conn.result)
return False
else:
return True
#user.route('/', methods=['GET', 'POST'])
#user.route('/login/', methods=['GET', 'POST'])
def login():
# global username
# username = None
# If POST, redirect to dashboard
if request.method == 'POST':
username = request.form['username'].encode('utf8').decode("utf-8")
password = request.form['password'].encode('utf8').decode("utf-8")
# Try to login using ldap
test = connect_ldap(username, password)
# Invalid credentials
if not test:
return render_template(
'login.html',
isinvalid='is-invalid',
error='Username or Password is incorrect'
)
else:
# session['user_id'] = request.form['username']
print('redict to home page')
return redirect(url_for('page.home'))
# If GET, render the login page
else:
return render_template('user/login.html')
page/views.py:
from flask import Blueprint, render_template
page = Blueprint('page', __name__, template_folder='templates')
#page.route('/home')
def home():
return render_template('page/home.html')
#page.route('/terms')
def terms():
return render_template('page/terms.html')
#page.route('/privacy')
def privacy():
return render_template('page/privacy.html')
I found a fix for this problem.
In order to better facilitate the generation of URLs that make use of an HTTPS URL
scheme this patch adds a parameter with this specific purpose in mind. To
achieve this we explicitly pass in a param, _scheme='https', and then set the
url_scheme attribute of our MapAdapter instance appropriately.
Importantly, _external=True must be set in order for this to work properly.
As such, failure to do so results in a ValueError being raised.
So, I just replace return redirect(url_for('page.home')) => return redirect(url_for('page.home', _external=True, _scheme='https'))
Reference: https://github.com/pallets/flask/commit/b5069d07a24a3c3a54fb056aa6f4076a0e7088c7

flask session not working if I use mkdtemp() for SESSION_FILE_DIR

I have a flask application in what I'm trying to create a session but only for the admin routes, so anywhere else can be accessible except admin routes, the thing is in my localhost everything works fine, but, in the live server, the session get created when I log in, but when I try to check if the session exist calling session.get("user_id"), it doesn't. Is like if the session is not created persistent. I have been reading for 10 hours today and I finally found why is not working, but I really don't undestand why it happens. The problem i'm facing is located in app.config["SESSION_FILE_DIR"] = mkdtemp(), if I use this in the localhost works, but not in the live server, and if I omite that line, then it works in the live server. Here is my code:
from flask import Flask, flash, redirect, render_template, request, session, jsonify, url_for
from flask_session import Session
from functools import wraps
from werkzeug.security import check_password_hash, generate_password_hash
from tempfile import mkdtemp # used to create a temp directory
from helpers import login_required
import os
# Configure application as flask app
app = Flask(__name__)
# Ensure templates are auto-reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True
# Configure session to use filesystem (instead of signed cookies)
app.config["SESSION_FILE_DIR"] = mkdtemp()
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
app.secret_key = 'superkey'
# function to login admin
#app.route("/login", methods=["GET", "POST"])
def login():
"""Log admin in"""
# Forget any user_id
session.clear()
# User reached route via POST (as by submitting a form via POST)
if request.method == "POST":
# Ensure username was submitted
if not request.form.get("username"):
flash ("must provide username")
return render_template('/login.html')
# Ensure password was submitted
elif not request.form.get("password"):
flash ("must provide password")
return render_template('/login.html')
# Query database for username
username = request.form.get("username")
username = str.lower(username)
db = dbConnection()
db.execute("SELECT * FROM identities WHERE user_name = ?", [username])
rows = db.fetchall()
# Ensure username exists and password is correct
if len(rows) == 1:
uid, name, pass_hash = rows[0]
if len(rows) != 1 or not check_password_hash(pass_hash, request.form.get("password")):
flash ("invalid username and/or password")
return render_template('/login.html')
# Remember which user has logged in
session["user_id"] = uid
# Redirect user to home page
return redirect("/adminForms")
# User reached route via GET (as by clicking a link or via redirect)
else:
return render_template("login.html")
# function as decorator to ensure the user was logged in before
# can go to a protected page
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
# if session don't return a value, then the user needs to log in
if session.get("user_id") is None:
return redirect("/login")
return f(*args, **kwargs)
return decorated_function

Can't write to MySQL DB

I'm a Flask newbie trying to create a simple app. I'm currently stuck at user registration where I'm trying to save data in database but it's not happening. However, the logging I'm doing indicates that the operation was a success. Could someone tell me what I'm doing wrong?
Here are portions of code that'll help you understand what I'm trying to do:
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
from flask.ext.mysqldb import MySQL
# Configuration
MYSQL_HOST = 'localhost'
MYSQL_USER = 'root'
MYSQL_PASSWORD = 'root'
MYSQL_DB = 'up2date'
DEBUG = True
SECRET_KEY =
'\xc6)\x0f\\\xc5\x86*\xd7[\x92\x89[\x95\xcfD\xfd\xc1\x18\x8e\xf1P\xf7_\r'
# Create the flask app
app = Flask(__name__)
app.config.from_object(__name__)
# Create instance for working with MySQL
mysql = MySQL(app)
# Function to connect to DB
def connect_db():
return mysql.connection.cursor()
# define functions that will make DB available automatically on each request
#app.before_request
def before_request():
g.db = connect_db()
#app.teardown_request
def teardown_request(exception):
g.db.close()
And finally, the code that performs user registration:
#app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
email = request.form['email']
password = request.form['password']
result = g.db.execute('INSERT INTO users (email, password) VALUES (%s, %s)', [email, password])
print(email, password)
print(result, " rows affected")
flash('Registration successful! You may log in now.')
return redirect(url_for('show_home'))
The two print statements confirm that the email address and password were captured correctly, and the result variable contains 1, indicating 1 row affected. But still there's no row in the DB. I earlier thought this had something to do with committing, but g.db.commit() throws error: AttributeError: 'Cursor' object has no attribute 'commit'
I assume you use MySQL-python.
connect_db() returns the cursor and not the connection. The cursor does not have a commit() function, as the exception says, however the connection has the commit function you need. I think you need to do this:
def connect_db():
return mysql.connection
For more info you can take a look at the code.

Resources