{"id":22533390,"url":"https://github.com/mgitrov/ldap-setup","last_synced_at":"2025-03-28T05:42:56.268Z","repository":{"id":249375488,"uuid":"830938163","full_name":"MGitrov/LDAP-setup","owner":"MGitrov","description":null,"archived":false,"fork":false,"pushed_at":"2024-07-20T17:19:34.000Z","size":22,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-02T06:41:25.610Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MGitrov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-19T09:58:49.000Z","updated_at":"2024-07-20T17:19:37.000Z","dependencies_parsed_at":"2024-07-20T10:46:56.850Z","dependency_job_id":"5367c42c-14cf-4998-abaa-dd8414c073f3","html_url":"https://github.com/MGitrov/LDAP-setup","commit_stats":null,"previous_names":["mgitrov/ldap-setup"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MGitrov%2FLDAP-setup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MGitrov%2FLDAP-setup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MGitrov%2FLDAP-setup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MGitrov%2FLDAP-setup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MGitrov","download_url":"https://codeload.github.com/MGitrov/LDAP-setup/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245978230,"owners_count":20703677,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-07T09:08:11.481Z","updated_at":"2025-03-28T05:42:56.249Z","avatar_url":"https://github.com/MGitrov.png","language":"Python","readme":"# Introduction\nThis repository demonstrates a setup for user authentication using LDAP combined with MFA in a Flask web application. The solution involves setting up an LDAP server with Docker, creating and managing users, and integrating an additional security layer using Time-based One-Time Passwords (TOTP) for MFA.\n\n# Prerequisites\n* Git\n* Docker\n\n# Directory Structure\n```\nLDAP-setup/\n├── Dockerfile\n├── docker-compose.yml\n├── init.ldif\n├── .env\n├── README.md\n└── app/\n    ├── templates/\n    │   ├── login.html\n    │   ├── setup_mfa.html\n    │   ├── mfa.html\n    │   └── welcome.html\n    └── app.py\n```\n\n# Key Components\n## Docker Compose\nDefines the services, environment variables, and volumes for setting up the LDAP server and the Flask web application.\n``` yaml\nversion: '3.8'\n\nservices:\n  ldap:\n    image: osixia/openldap:latest\n    container_name: ldap-server\n    environment:\n      LDAP_ORGANISATION: ${LDAP_ORGANISATION} # Sets the organization name for the LDAP directory.\n      LDAP_DOMAIN: ${LDAP_DOMAIN} # Sets the domain components (DC) for the LDAP directory.\n      LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD} # Sets the password for the LDAP admin user.\n    ports:\n      - \"389:389\" # For LDAP.\n    volumes:\n      - ldap_data:/var/lib/ldap\n      - ldap_config:/etc/ldap/slapd.d\n      - ./init.ldif:/container/service/slapd/assets/config/bootstrap/ldif/50-bootstrap.ldif # Bind mount for initial LDIF file.\n    command: --copy-service # Ensures that the \"init.ldif\" file is copied into the appropriate directory before the LDAP server starts.\n\n  ldap-auth:\n    build: .\n    container_name: ldap-auth\n    ports:\n      - \"5000:5000\"\n    depends_on:\n      - ldap\n\nvolumes:\n  ldap_data:\n  ldap_config:\n```\n## LDAP Configuration\nInitializes the LDAP directory with an organizational unit and users.\n``` ldif\ndn: ou=Users,dc=my-domain,dc=com\nobjectClass: organizationalUnit\nou: Users\n\ndn: uid=maxim.petrov,ou=Users,dc=my-domain,dc=com\nobjectClass: inetOrgPerson\ncn: Maxim Petrov\nsn: Petrov\ngivenName: Maxim\nmail: maxim.petrov@my-domain.com\nuid: maxim.petrov\nuserPassword: mypassword\n\ndn: uid=israel.israeli,ou=Users,dc=my-domain,dc=com\nobjectClass: inetOrgPerson\ncn: Israel Israeli\nsn: Israeli\ngivenName: Israel\nmail: israel.israeli@my-domain.com\nuid: israel.israeli\nuserPassword: password123\n```\n## Flask Web Application\nThe main application file for the Flask web application, handling routes and authentication logic.\n``` python\nfrom flask import Flask, request, render_template, redirect, url_for, flash, session\nfrom ldap3 import Server, Connection, SIMPLE\nimport pyotp # Library used for generating and verifying TOTP tokens for MFA.\nimport qrcode # Library used for generating QR codes.\n\n# io and base64 are used for handling image data for the QR code.\nimport io\nimport base64\n\napp = Flask(__name__)  # Initiates a Flask application instance.\napp.secret_key = \"dummy_secret_key\" # A random string used by Flask to secure things like sessions, cookies, etc.\n\nLDAP_SERVER = \"ldap://ldap-server\"  # The URL of the LDAP server.\n# As the LDAP server is running on a container, I need to specify the name of the container as part of the URL.\n\nBASE_DN = \"dc=my-domain,dc=com\"  # The base distinguished name (DN) for the LDAP directory. Represents the root of the LDAP tree.\n# In other words, specifies the starting point in the LDAP directory tree for searches.\n\n# What DN is used to login to the LDAP directory to perform searches and other operations.\nBIND_DN = \"cn=admin,dc=my-domain,dc=com\"\nBIND_PASSWORD = \"admin_password\"\n\nuser_secrets = {} # Stores each user's unique generated secret, keyed by the username.\n\n@app.route(\"/\")\ndef index():\n    if \"authenticated\" in session:\n        return redirect(url_for(\"welcome\"))\n    return redirect(url_for(\"login\"))\n\n@app.route(\"/login\", methods=[\"GET\", \"POST\"])\ndef login():\n    if request.method == \"POST\":\n        username = request.form[\"username\"]\n        password = request.form[\"password\"]\n        user_dn = f\"uid={username},ou=Users,{BASE_DN}\"  # Constructs user's DN based on the provided username and the \"BASE_DN\".\n\n        try:\n            server = Server(LDAP_SERVER)\n            connection = Connection(server, user=user_dn, password=password, authentication=SIMPLE)  # Connects to the LDAP server using\n            # a plaintext username and password for authentication.\n\n            if connection.bind():\n                session[\"username\"] = username  # Stores the username in the current session.\n\n                if username not in user_secrets:\n                    secret = pyotp.random_base32()  # Generates a new secret (a random string of characters encoded in base32) for the current user.\n                    user_secrets[username] = secret # Assigns the generated secret to the current user.\n                    session[\"setup_mfa\"] = True\n                    return redirect(url_for(\"setup_mfa\"))  # Redirects the user the to MFA setup.\n\n                return redirect(url_for(\"mfa\"))  # Redirects to MFA verification if the user already has a secret generated for him.\n\n            else:\n                flash(\"Invalid credentials\", \"danger\")\n                return redirect(url_for(\"login\"))\n\n        except Exception as error:\n            flash(f\"Error: {str(error)}\", \"danger\")\n            return redirect(url_for(\"login\"))\n\n    return render_template(\"login.html\") # Handles the GET request by rendering the login form.\n\n@app.route(\"/setup_mfa\")\ndef setup_mfa():\n    if \"username\" not in session or \"setup_mfa\" not in session: # Checks if the user is logged in.\n        return redirect(url_for(\"login\"))\n\n    username = session[\"username\"]\n    secret = user_secrets[username]\n    otp_uri = pyotp.totp.TOTP(secret).provisioning_uri(name=username, issuer_name=\"ldap-setup\") # Creates the TOTP URI out of the user's\n    # unique generated secret for the authenticator app to generate the TOTP token.\n\n    img = qrcode.make(otp_uri) # Creates the QR code out of the TOTP URI created above.\n    \n    # Creates a buffer in memory to store the QR code image (instead of a file on disk).\n    buf = io.BytesIO()\n    img.save(buf, format=\"PNG\")\n\n    img_str = base64.b64encode(buf.getvalue()).decode(\"ascii\") # Converts the QR code image to a Base64 string to embed it in the HTML.\n\n    return render_template(\"setup_mfa.html\", qr_code=img_str)\n\n@app.route(\"/mfa\", methods=[\"GET\", \"POST\"])\ndef mfa():\n    if \"username\" not in session: # Checks if the user is logged in.\n        return redirect(url_for(\"login\"))\n\n    if request.method == \"POST\":\n        token = request.form[\"token\"] # User's input token will be processed.\n        username = session[\"username\"]\n\n        secret = user_secrets[username]\n        totp = pyotp.TOTP(secret) # Generates the TOTP token for comparison with user's input token.\n        if totp.verify(token):\n            session.pop(\"setup_mfa\", None) # Removes the \"setup_mfa\" flag.\n            session[\"authenticated\"] = True  # Sets the \"authenticated\" flag to true.\n            flash(\"Login successful\", \"success\")\n            return redirect(url_for(\"welcome\"))\n\n        else:\n            flash(\"Invalid MFA token\", \"danger\")\n\n    return render_template(\"mfa.html\")\n\n@app.route(\"/welcome\")\ndef welcome():\n    if \"authenticated\" not in session:\n        return redirect(url_for(\"login\"))\n\n    return render_template(\"welcome.html\")\n\n@app.route(\"/logout\", methods=[\"POST\"])\ndef logout():\n    session.pop(\"username\", None)\n    session.pop(\"authenticated\", None)\n    return redirect(url_for(\"login\"))\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=5000)  # Allows the Flask server to be accessed from any IP address.\n```\n### Routes\n| Route        | Description                                                         |\n| ------------ | ------------------------------------------------------------------- |\n| `/`          | Displays the login page and handles user login.                     |\n| `/setup_mfa` | Displays the QR code for the user to scan and set up TOTP-based MFA.|\n| `/mfa`       | Displays the page for entering the TOTP token and verifies the token.|\n| `/welcome`   | Displays a welcome message after the user is authenticated.         |\n| `/logout`    | Logs the user out and clears the session.                           |\n\n\n# Deployment\n1. Open the Terminal and run the following command:\n``` bash\ngit clone https://github.com/MGitrov/LDAP-setup.git\n```\n2. Then you will have to go inside the \"LDAP-setup\" folder using the following command:\n```bash\ncd LDAP-setup\n```\n3. To start services, you will have to enter the following command:\n```bash\ndocker-compose up -d\n```\n4. You may access the web application via your browser by typing the following address:\n```bash\nhttp://localhost:5000\n```\n5. Log in using the LDAP credentials specified in the ```init.ldif``` file:\n* Username: maxim.petrov\n* Password: mypassword\n\n  or\n* Username: israel.israeli\n* Password: password123\n6. Upon first login, scan the QR code with an authentication application (e.g., Google Authenticator, Authy, Microsoft Authenticator).\n  \n7. Enter the TOTP token (the 6 digit number) generated by the application.\n   \n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgitrov%2Fldap-setup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmgitrov%2Fldap-setup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgitrov%2Fldap-setup/lists"}