How to do LDAP authentication in flask restful - python

I need to create LDAP Authentication for a Rest API used within the network of an organization.
I thought I might be able to do this, creating an AuthUtility class :
class AuthUtility:
def __init__(self, user_model, user_data):
self.username = user_model.username
self.password = user_model.password
self.parsed_username = user_data.username
self.parsed_password = user_data.password
self.parsed_auth_method = user_data.auth_method
def _is_ldap_authenticated(self) -> bool:
# TODO: To be replaced by ldap authentication
return False # Hard-Coded
def _is_db_authenticated(self) -> bool:
return self.password == self.parsed_password
def is_authenticated(self) -> bool:
auth_method = self.parsed_auth_method
if auth_method == "ldap":
return self._is_ldap_authenticated()
elif auth_method == "db":
return self._is_db_authenticated()
else:
# This should never happen
print(f"Method {auth_method} not defined!")
# TODO: Return error! This should never happen!
return False
class UserModel(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), nullable=False)
password = db.Column(db.String(80), nullable=False)
#classmethod
def find_all(cls) -> List["UserModel"]:
return cls.query.all()
#classmethod
def find_by_username(cls, username: str) -> "UserModel":
return cls.query.filter_by(username=username).first()
#classmethod
def find_by_id(cls, _id: int) -> "UserModel":
return cls.query.filter_by(id=_id).first()
def save_to_db(self) -> None:
db.session.add(self)
db.session.commit()
def delete_from_db(self) -> None:
db.session.delete(self)
db.session.commit()
And in my post request to the login endpoint, include a key auth_method whose value can be either ldap or db
BODY:
{
"username": "myusername",
"password": "mypassword",
"auth_method": "db"
}
or
BODY:
{
"username": "myusername",
"password": "mypassword",
"auth_method": "ldap"
}
class UserLogin(Resource):
#classmethod
def post(cls):
request_json = request.get_json()
# Pop auth_method from request_json
try:
auth_method = request_json.pop("auth_method")
except KeyError:
return {"message": "Authentication method should be defined"}
user_data = user_schema.load(request_json)
# Find user in the database
user = UserModel.find_by_username(user_data.username)
# Add back auth_method
user_data.auth_method = auth_method
# Check if the user with username exist in the database
if not user:
return {"message": "user_user_not_found"}, 401
# Check to see the user is authenticated
elif not AuthUtility(user, user_data).is_authenticated():
return {"message": "user_invalid_credentials"}, 401
# Create and return access_token and refresh_token
access_token = create_access_token(user.id, fresh=True)
refresh_token = create_refresh_token(user.id)
return {"access_token": access_token, "refresh_token": refresh_token}, 200
The user can log in through either db or ldap auth_methods.
db is clear to me but I do not know how to do it with ldap.
Any idea how to actually complete it and actually using it in production?
I also have found some python ldap3 library but really not sure how to use it
I need to write this part:
def _is_ldap_authenticated(self) -> bool:
# TODO: To be replaced by ldap authentication
return False # Hard-Coded
But the question is, how and is there any resource available I can use and see how to do it?
To add some detail about ldap, please note that I have done this test and it connects:
from ldap3 import SAFE_SYNC, Server, Connection, ALL
ldsp_server = "ldap://***.***.com"
root_dn = "dc=***,dc=com"
ldap_user_name = "***"
ldap_password = "***"
def ldap_test():
server = Server(ldsp_server, get_info=ALL)
conn = Connection(
server,
user=ldap_user_name,
password=ldap_password,
client_strategy=SAFE_SYNC,
auto_bind=True,
)

Related

Flask-smorest returning an empty json string

The JSON response of my endpoint returns {} even though I am logging the correct data.
from flask_smorest import Blueprint
bp = Blueprint("auth", __name__, url_prefix="/api/v1/auth/")
#bp.route("/login", methods=["POST"])
#bp.arguments(LoginRequest)
#bp.response(200, JwtTokenResponse)
#bp.response(404, ErrorResponse)
def login(args):
current_app.logger.debug(args)
username = args.get("username", None)
password = args.get("password", None)
current_app.logger.debug(f"Username: {username}")
current_app.logger.debug(f"Password: {password}")
user = User.query.filter_by(username=username).first()
if user is None:
return dict(message="User does not exists"), 404
if not check_password_hash(user.password, password):
return dict(message="Unable to Authenticate user."), 404
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
response = dict(access_token=access_token, refresh_token=refresh_token)
current_app.logger.debug(f"Response: {response}")
return response, 200
My LoginTokenSchema and ErrorResponse schemas are defined as:
from marshmallow import Schema, fields
class JwtTokenResponse(Schema):
access_token = fields.String()
refresh_token = fields.String()
class ErrorResponse(Schema):
message = fields.String()
When I test the API with a user not in the database or with the wrong password; it will product the correct response with ErrorRespose however with the correct creds it just will output {}, when I check the flask logs I can see access/refresh token dict, what am I doing wrong?
You don't need to call Blueprint.response twice, which is what I did in my question. You can call alt_response with a custom error message or just use abort and let flask-smorest take care of the rest.
#bp.route("/login", methods=["POST"])
#bp.arguments(LoginRequest)
#bp.response(200, SmorestJWTTokenResponse)
def login(args):
current_app.logger.debug(args)
username = args.get("username", None)
password = args.get("password", None)
user = User.query.filter_by(username=username).first()
if user is None:
abort(404, message="User does not exists.")
if not check_password_hash(user.password, password):
abort(404, message="Unable to Authenticate user.")
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
return dict(access_token=access_token, refresh_token=refresh_token), 200

In pytesting how do I create and delete the database while having assertions inbetween.I tried yield but yield will not work in new versions of pytest

I could always rollback to an older pytest version but how do I do this with the newest version of pytest?
For a simple explanation
# create database
db.session.add(new_user)
db.session.commit()
# in between code that sometimes has an assertion error.
# delete database
db.session.delete(new_user) # In the try in the except block db.session.rollback()
db.session.commit()
The problem is the in-between code. I tried a try instead of yield but the try doesn't give an assertion error. Also any alternative ideas is appreciated if someone has a better solution. I also just want to state when I run python -m pytest the code runs except the code with the function yield.
Any help is appreciated.
Here is the code below.
models.py
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
hashed_password = db.Column(db.String(128), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
confirmation_email = db.Column(db.Boolean, default=False, nullable=False)
reset_email_password = db.Column(db.Boolean, default=False, nullable=False)
def __init__ (self ,username: str, email: str, hashed_password: str, confirmation_email=False, reset_email_password=False):
self.username = username
self.email = email
self.hashed_password = hashed_password
self.confirmation_email = confirmation_email
self.reset_email_password = reset_email_password
def create_token(self, expires_sec=1800):
# Serializer passes in SECRET_KEY 30 min beacuse of expir_sec.
SECRET_KEY = os.urandom(32)
s = Serializer (SECRET_KEY, expires_sec)
# Creates randomly assigned token as long as less then 30 min
return s.dumps({'user_id': self.id}).decode('utf-8')
#staticmethod
def verify_token(token):
# Serializer passes in SECRET_KEY
SECRET_KEY = os.urandom(32)
s = Serializer(SECRET_KEY)
try:
'''
get user id by running s.loads(token).if this line works
If it does not work returns error and return none in the except block
'''
user_id = s.loads(token)['user_id']
except:
# flash('That is an invalid or expired token')
return None
# why query.get? Because "u = User.query.get(1)" gives the current user.
return User.query.get(user_id)
config.py
class Config(object):
SECRET_KEY = os.urandom(32)
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
DEBUG = True
TESTING = False
WTF_CSRF_ENABLED = True
class TokenPytestConfig(Config):
WTF_CSRF_ENABLED = False
TESTING = True
conftest.py
#pytest.fixture()
def new_user():
plaintext_password = 'pojkp[kjpj[pj'
hashed_password = bcrypt.hashpw(plaintext_password.encode('utf-8'), bcrypt.gensalt())
current_user = User(username='fkpr[kfkuh', hashed_password=hashed_password, email=os.environ['TESTING_EMAIL_USERNAME'],
confirmation_email=False, reset_email_password=False)
return current_user
token_app = create_app(TokenPytestConfig)
#pytest.fixture()
def token_client():
# make_app_run_in_test_env = client
return token_app.test_client()
#pytest.fixture()
def token_runner():
return token_app.test_cli_runner()
test_routes.py
token_app = create_app(TokenPytestConfig)
token_app.app_context().push()
def test_verified_email(token_client, new_user):
'''
GIVEN a Flask application configured for testing
WHEN the "/verified_email<token>" request is (GET) Also test the token is c
THEN check that a token works.
'''
response = token_client.get("/verified_email<token>", follow_redirects=True
assert response.status_code == 200
with token_app.test_request_context():
# Create the database and the database table
db.create_all(token_app)
# Insert user data
db.session.add(new_user)
# Commit the changes for the users
db.session.commit()
'''
yield freezes till the functions ends.
This also allows you to create and delete the database
while putting code inbetween
'''
yield db.drop_all(new_user)
user = User.query.filter_by(username=new_user.id).first()
token = user.create_token()
print(token)
assert token == 'ffff' # assert user?
verify_token = User.verify_token(token)
print(verify_token)
assert verify_token == None
Thanks

Restric logged users to access unwanted url routes in a tornado server

I am trying to restrict logged user to access URL routes that are not assigned to them. As soon a user had logged, for example user1, will be redirected to https://myurl.com/user1. So far, that works good, but I would like to avoid that user1 can see the content in the route of user2 in https://myurl.com/user2.
Below you can see the code I am currently using.
import tornado
from tornado.web import RequestHandler
import sqlite3
# could define get_user_async instead
def get_user(request_handler):
return request_handler.get_cookie("user")
# could also define get_login_url function (but must give up LoginHandler)
login_url = "/login"
db_file = "user_login.db"
connection = None
cursor = None
# optional login page for login_url
class LoginHandler(RequestHandler):
def get(self):
try:
errormessage = self.get_argument("error")
except Exception:
errormessage = ""
self.render("login.html", errormessage=errormessage)
def check_permission(self, username, password):
connection = sqlite3.connect(db_file)
cursor = connection.cursor()
cursor.execute("SELECT * FROM users WHERE username=? AND password=?", (username, password))
data = cursor.fetchone()
if username == data[1] and password == data[2]:
return True
return False
def post(self):
username = self.get_argument("username", "")
password = self.get_argument("password", "")
auth = self.check_permission(username, password)
if auth:
self.set_current_user(username)
self.redirect(self.get_argument("next", f"/{username}"))
else:
error_msg = "?error=" + tornado.escape.url_escape(
"Login failed, please try again or contact your system administrator."
)
self.redirect(login_url + error_msg)
def set_current_user(self, user):
if user:
self.set_cookie("user", tornado.escape.json_encode(user))
else:
self.clear_cookie("user")
# optional logout_url, available as curdoc().session_context.logout_url
# logout_url = "/logout"
# optional logout handler for logout_url
class LogoutHandler(RequestHandler):
def get(self):
self.clear_cookie("user")
self.redirect(self.get_argument("next", "/"))

While using JWT module i am getting an error "user does not exists error"

I am learning simple python JWT module. I am creating a token using restfull api and then using the same token to get the data, But unfortunately i am getting an error "User does not exist".
Python Code snippet
class User:
def __init__(self, _id, username, password):
self.id = _id
self.username = username
self.password = password
users = [
User(1, "bob", "asdf")
]
username_mapping = {u.username: u for u in users}
userid_mapping = {u.id: u for u in users}
def authenticate(username, password):
user = username_mapping.get(username, None)
if user and user.password == password:
return user
def identity(payload):
user_id = payload["identity"]
return username_mapping.get(user_id, None)
##Using this function for authentication
app = Flask(__name__)
app.secret_key = "jose"
api = Api(app)
jwt = JWT(app,authenticate, identity)
def abort_if_item_not_exist(name):
item = next(filter(lambda x: x["name"] == name, items), None)
if item == None:
abort(404, message = "This item does not exist")
else:
return {"item": item}
class Item(Resource):
#jwt_required()
def get(self,name):
return abort_if_item_not_exist(name)
I have found the answer, thanks to jsp, he pointed a website. I analyzed and got the payload identity id. Then I realized I am getting an ID and I am comparing it with wrong value. it is my typo only.
def identity(payload):
user_id = payload["identity"]
return userid_mapping.get(user_id, None) # This needs to be changed

Unauthorized access and token not working

I have been struggling while trying to get Flask Authentication work .
I'm using this example: https://github.com/miguelgrinberg/REST-auth/blob/master/api.py
I have a user in mysql database, I can generate a token but everytime I make a cURL request with it, I get Access Unauthorized with Exception Bad Signature.
Here is my code:
Users.py:
# coding=utf-8
from sqlalchemy import Column, String, Date, Integer, Numeric, Enum
from common.base import Base
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import relationship, deferred
from model.DictSerializable import DictSerializable
from passlib.apps import custom_app_context as pwd_context
from itsdangerous import (TimedJSONWebSignatureSerializer
as Serializer, BadSignature, SignatureExpired)
class Users( Base, DictSerializable ):
__tablename__ = 'users'
id = Column(Integer, primary_key = True)
username = Column(String(32), index = True)
password_hash = Column(String(128))
def __init__( self, username, password ):
self.username = username
self.password = password
def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)
def generate_auth_token(self, expiration=3600):
s = Serializer('SECRET KEY', expires_in=expiration)
return s.dumps({'id': self.id})
#staticmethod
def verify_auth_token(token):
s = Serializer('SECRET KEY')
try:
data = s.loads(token)
except SignatureExpired:
return None # valid token, but expired
except BadSignature:
print("Bad signature")
return None # invalid token
user = session.query(User).filter(User.id == data['id']).first()
return user
app.py :
auth = HTTPBasicAuth('Bearer')
#auth.error_handler
def unauthorized():
return make_response(jsonify({'error': 'Unauthorized access'}), 403)
#app.route('/v1/api/users', methods = ['POST'])
def new_user():
username = request.json.get('username')
password = request.json.get('password')
if username is None or password is None:
abort(400) # missing arguments
if session.query(Users).filter(Users.username == username).first() is not None:
abort(400) # existing user
user = Users(username, password)
user.hash_password(password)
session.add(user)
session.commit()
return jsonify({ 'username': user.username }), 201, {'Location': url_for('get_user', id = user.id, _external = True)}
#app.route('/v1/api/users/<int:id>')
def get_user(id):
user = session.query(Users.id).all()
if not user:
abort(400)
return jsonify({'username': user.username})
#auth.verify_password
def verify_password(username_or_token, password):
# first try to authenticate by token
user = Users.verify_auth_token(username_or_token)
if not user:
# try to authenticate with username/password
user = session.query(Users).filter_by(username=username_or_token).first()
if not user or not user.verify_password(password):
return False
g.user = user
return True
#app.route('/v1/api/token')
#auth.login_required
def get_auth_token():
token = g.user.generate_auth_token(3600)
return jsonify({'token': token.decode('ascii'), 'duration': 3600})
Here is the using case in the terminal using cURL:
vagrant#vagrant-ubuntu-trusty-64:~/projects$ curl -u souad:souad -i -X GET http://localhost:5000/v1/api/token
HTTP/1.0 401 UNAUTHORIZED
Content-Type: text/html; charset=utf-8
Content-Length: 19
WWW-Authenticate: Bearer realm="Authentication Required"
Server: Werkzeug/0.14.1 Python/2.7.6
Date: Wed, 03 Oct 2018 11:30:04 GMT
Output of the API:
[2018-10-03 10:01:36,253] INFO in _internal: 127.0.0.1 - - [03/Oct/2018 10:01:36] "GET /v1/api/token HTTP/1.1" 401 -
bad signature
[2018-10-03 11:30:04,710] INFO in _internal: 127.0.0.1 - - [03/Oct/2018 11:30:04] "GET /v1/api/token HTTP/1.1" 401 -
I don't know why I get Exception Bad Signature ???
What is the easiest way to authenticate by Flask and protect some endpoints ??

Categories

Resources