From 7ae29b3fbc260e159e0bf8fad965b935cce1403a Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:41:00 -0500 Subject: [PATCH] Add Microsoft Entra ID authentication with OAuth2 --- README.md | 41 ++++++++++++++++++++++++++++++++----- pdf_renamer.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++- requirements.txt | 4 +++- 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c0dc645..06fe0dd 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,33 @@ A Python web application built with Flask that automatically renames PDF files b - Python 3.6 or higher - Virtual environment (recommended) +- Microsoft Entra ID (Azure AD) tenant with app registration + +## Microsoft Entra ID Setup + +1. **Register an application** in Azure Portal: + - Go to Azure Active Directory > App registrations > New registration + - Name: PDF Renamer App + - Supported account types: Accounts in this organizational directory only + - Redirect URI: `http://localhost:3000/auth` (for local development) + +2. **Configure authentication**: + - In the app registration, go to Authentication + - Add redirect URI if not set: `http://localhost:3000/auth` + - Enable "Access tokens" and "ID tokens" if needed + +3. **Get credentials**: + - Client ID: From Overview page + - Tenant ID: From Overview page + - Client Secret: Go to Certificates & secrets > New client secret + +4. **Set environment variables**: + ```bash + export TENANT_ID="your-tenant-id" + export CLIENT_ID="your-client-id" + export CLIENT_SECRET="your-client-secret" + export SECRET_KEY="your-random-secret-key" + ``` ## Installation @@ -41,19 +68,23 @@ A Python web application built with Flask that automatically renames PDF files b ## Usage -1. **Run the application**: +1. **Set environment variables** (see Entra ID Setup above) + +2. **Run the application**: ```bash python pdf_renamer.py ``` -2. **Access the web interface**: - Open your browser to `http://127.0.0.1:3000/` +3. **Access the web interface**: + - Open `http://127.0.0.1:3000/` + - You'll be redirected to Microsoft login + - After authentication, you'll access the app -3. **Upload files**: +4. **Upload files**: - Select individual PDF files or ZIP archives containing PDFs - Click "Process PDFs" -4. **Download results**: +5. **Download results**: - Download the `renamed_files.zip` containing all processed PDFs - Review any skipped files listed on the results page diff --git a/pdf_renamer.py b/pdf_renamer.py index f60540e..883ae30 100644 --- a/pdf_renamer.py +++ b/pdf_renamer.py @@ -1,16 +1,41 @@ import os import tempfile import zipfile -from flask import Flask, request, render_template_string, send_from_directory +from functools import wraps +from flask import Flask, request, render_template_string, send_from_directory, redirect, url_for, session from werkzeug.utils import secure_filename from PyPDF2 import PdfReader +from authlib.integrations.flask_client import OAuth app = Flask(__name__) app.config['UPLOAD_FOLDER'] = tempfile.mkdtemp() app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB limit +app.secret_key = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') + +# Entra ID Configuration +TENANT_ID = os.getenv('TENANT_ID', 'your-tenant-id') +CLIENT_ID = os.getenv('CLIENT_ID', 'your-client-id') +CLIENT_SECRET = os.getenv('CLIENT_SECRET', 'your-client-secret') + +oauth = OAuth(app) +microsoft = oauth.register( + name='microsoft', + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + server_metadata_url=f'https://login.microsoftonline.com/{TENANT_ID}/v2.0/.well-known/openid_configuration', + client_kwargs={'scope': 'openid profile email'} +) ALLOWED_EXTENSIONS = {'pdf', 'zip'} +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user' not in session: + return redirect(url_for('login')) + return f(*args, **kwargs) + return decorated_function + def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @@ -27,7 +52,26 @@ def extract_names_from_pdf(pdf_path): except Exception as e: return None +@app.route('/login') +def login(): + redirect_uri = url_for('auth', _external=True) + return microsoft.authorize_redirect(redirect_uri) + +@app.route('/auth') +def auth(): + token = microsoft.authorize_access_token() + user = microsoft.parse_id_token(token) + session['user'] = user + return redirect(url_for('upload_files')) + +@app.route('/logout') +@login_required +def logout(): + session.pop('user', None) + return redirect(url_for('login')) + @app.route('/', methods=['GET', 'POST']) +@login_required def upload_files(): if request.method == 'POST': files = request.files.getlist('files') @@ -104,9 +148,12 @@ def upload_files(): a:hover { background-color: #0056b3; } ul { list-style-type: none; padding: 0; } li { background-color: #fff; margin: 5px 0; padding: 10px; border-radius: 5px; box-shadow: 0 0 5px rgba(0,0,0,0.1); } + .logout { position: absolute; top: 20px; right: 20px; background-color: #dc3545; } + .logout:hover { background-color: #c82333; }
+ Logout