cp2025 計算機程式

  • Home
    • SMap
    • reveal
    • blog
  • About
    • cs101
    • Computer
      • llama
      • nginx
    • AI
      • QandA
      • Teams
    • Homework
  • Topics
    • Git
      • Git_ex1
      • Git_ex2
    • Python
      • SE
      • CL
      • Loops
      • Recursion
      • SA
      • SST
      • Maze
      • Collect
      • GT
      • Python_ex1
    • Javascript
      • HTML and CSS
    • Project
      • Waitress
      • API
  • Brython
    • Brython_ex
    • Robot_ex
  • Ref
    • Reeborg
      • ex1
      • Otto_ninja
    • tkinter
    • Pyodide
    • Pyodide_ex
    • Pyodide2
      • robot.py
      • Example2
    • Pyodide3
      • png_files
      • Harvest
Project << Previous Next >> API

Waitress

採 waitress 執行 app.py

start_app.py 程式碼:

from waitress import serve
from app import app  # 這裡導入 app.py 中的 Flask 應用

# 這裡的 127.0.0.1:5000 是服務監聽的地址與端口
serve(app, listen='127.0.0.1:5000', threads=8)

利用 waitress 執行的 app.py 程式碼:

# pip install Flask google-api-python-client google-auth
from flask import Flask, render_template, request, redirect, url_for, flash
from google.oauth2 import service_account
from googleapiclient.discovery import build
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)

# 組態設定
DOMAIN = "your_google_workspace_domain"
ADMIN_EMAIL = "yout_admin_email"
SERVICE_ACCOUNT_FILE = "C:/your_service_account_file_directory/service.json"
PASSWORD_SECRET = "your_passwod_secret"
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user']

def create_user(email_prefix, password):
    credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)
    delegated_credentials = credentials.with_subject(ADMIN_EMAIL)
    service = build('admin', 'directory_v1', credentials=delegated_credentials)
    user_body = {
        "primaryEmail": f"{email_prefix}@{DOMAIN}",
        "name": {
            "givenName": email_prefix,
            "familyName": "User"
        },
        "password": password
    }
    user = service.users().insert(body=user_body).execute()
    return user

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        secret = request.form['secret']
        prefix = request.form['prefix']
        account_password = request.form['account_password']
        confirm_password = request.form['confirm_password']

        # 權限驗證
        if secret != PASSWORD_SECRET:
            flash("通關密碼錯誤!")
            return redirect(url_for('index'))
        
        if not prefix.isdigit():
            flash("請輸入您的學號")
            return redirect(url_for('index'))
        
        # 密碼確認
        if account_password != confirm_password:
            flash("兩次密碼不一致,請重新確認!")
            return redirect(url_for('index'))
        
        # 密碼驗證(可加入更多規則)
        if len(account_password) < 8:
            flash("密碼長度必須至少 8 個字元!")
            return redirect(url_for('index'))
        
        try:
            result = create_user(prefix, account_password)
            flash(f"帳號建立成功: {result['primaryEmail']}")
        except Exception as e:
            flash(f"建立帳號失敗: {str(e)}")
        return redirect(url_for('index'))
    return render_template('form.html')

# 不使用 app.run(),讓 waitress 來處理

配合使用的 nginx.conf

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       [::]:80 ipv6only=on;
        server_name  your_first_server_domain your_server_cname_domain;

        #root   html;
        # 將 port 80 http 連結到倉儲的靜態網站
        root C:/your_static_site_directory/wcm2025_hw;
        index  index.html;

        location / {
            try_files $uri $uri/ =404;
        }
    }

    server {
        listen       [::]:443 ssl ipv6only=on;
        http2        on;
        server_name  your_first_server_domain your_server_cname_domain;

        ssl_certificate      fullchain.pem;
        ssl_certificate_key  privkey.pem;
        ssl_protocols        TLSv1.2 TLSv1.3;
        ssl_ciphers          HIGH:!aNULL:!MD5;

        location / {
            # https port 443 的連結資料是由近端 IPv6 or IPv4 port 8080 所提供
            #proxy_pass         http://[::1]:5000;
            proxy_pass         http://127.0.0.1:5000;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
        }
    }
}

初步測試所使用的單一通關密碼進行驗證,可以再透過 flask-pyoidc 改為 OIDC 認證通關:

先安裝必要模組:

pip install Flask flask-pyoidc google-api-python-client google-auth

接著登入 Google Cloud Console 建立 OAuth 2.0 憑證:

  1. 登入 Google Cloud Console
  2. 選擇你的專案,點選「建立憑證」>「OAuth 用戶端 ID」
  3. 選擇「Web 應用程式」
  4. 設定授權的重新導向 URI(Redirect URI)
  5. 例如:http://localhost:5000/oidc/callback
  6. 取得「用戶端 ID」與「用戶端密鑰」

接著建立 OIDC 設定檔:

將下列內容儲存為 oidc_secrets.json(請填入你自己的 client_id/client_secret):

{
  "web": {
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "issuer": "https://accounts.google.com",
    "redirect_uris": ["http://localhost:5000/oidc/callback"]
  }
}

利用 OIDC 認證通關的 app.py:

# pip install Flask flask-pyoidc google-api-python-client google-auth
from flask import Flask, render_template, request, redirect, url_for, flash, session
from flask_pyoidc import OIDCAuthentication
from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata
from google.oauth2 import service_account
from googleapiclient.discovery import build
import os
import json

app = Flask(__name__)
app.secret_key = os.urandom(24)

# === OIDC 設定 ===
with open('oidc_secrets.json') as f:
    oidc_conf = json.load(f)['web']

issuer = oidc_conf['issuer']
client_metadata = ClientMetadata(
    client_id=oidc_conf['client_id'],
    client_secret=oidc_conf['client_secret'],
    post_logout_redirect_uris=[oidc_conf['redirect_uris'][0]]
)
provider_config = ProviderConfiguration(
    issuer=issuer,
    client_metadata=client_metadata
)
auth = OIDCAuthentication({'default': provider_config}, app)

# === Google Workspace 設定 ===
DOMAIN = "your_google_workspace_domain"
ADMIN_EMAIL = "your_admin_email"
SERVICE_ACCOUNT_FILE = "C:/your_service_account_file_directory/service.json"
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user']

def create_user(email_prefix, password):
    credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)
    delegated_credentials = credentials.with_subject(ADMIN_EMAIL)
    service = build('admin', 'directory_v1', credentials=delegated_credentials)
    user_body = {
        "primaryEmail": f"{email_prefix}@{DOMAIN}",
        "name": {
            "givenName": email_prefix,
            "familyName": "User"
        },
        "password": password
    }
    user = service.users().insert(body=user_body).execute()
    return user

@app.route('/logout')
def logout():
    # 清除 session 並 redirect 回首頁
    session.clear()
    return redirect(auth.client_end_session_uri('default', url_for('index', _external=True)))

@app.route('/', methods=['GET', 'POST'])
@auth.oidc_auth('default')
def index():
    # 取得登入者資訊
    user_session = auth.user_session
    user_email = user_session.userinfo['email']

    # 只允許特定網域
    if not user_email.endswith('@your_google_workspace_domain'):
        return "無權限,請使用組織帳號登入", 403

    if request.method == 'POST':
        prefix = request.form['prefix']
        account_password = request.form['account_password']
        confirm_password = request.form['confirm_password']

        if not prefix.isdigit():
            flash("請輸入您的學號")
            return redirect(url_for('index'))

        if account_password != confirm_password:
            flash("兩次密碼不一致,請重新確認!")
            return redirect(url_for('index'))

        if len(account_password) < 8:
            flash("密碼長度必須至少 8 個字元!")
            return redirect(url_for('index'))

        try:
            result = create_user(prefix, account_password)
            flash(f"帳號建立成功: {result['primaryEmail']}")
        except Exception as e:
            flash(f"建立帳號失敗: {str(e)}")
        return redirect(url_for('index'))
    return render_template('form.html', user_email=user_email)

配合 OIDC 認證的 form.html:

<!doctype html>
<title>建立 Google Workspace 帳號</title>
<h2>建立 Google Workspace 帳號</h2>
<p>目前登入:{{ user_email }}</p>
<form method="POST">
  <label>學號(Email @ 前):<input type="text" name="prefix" required pattern="\d+"></label><br>
  <label>新帳號密碼:<input type="password" name="account_password" required></label><br>
  <label>再次輸入密碼:<input type="password" name="confirm_password" required></label><br>
  <input type="submit" value="送出">
</form>
{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul>
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}
<a href="{{ url_for('logout') }}">登出</a>

之後假設透過收集用戶的 email address 存為 allowed_email.txt, 採 OIDC 認證流程時,只允許電子郵箱在此檔案中的用戶有權限利用 app.py 建立 Google Workspace 帳號, 則 app.py 配合修改如下:

from flask import Flask, render_template, request, redirect, url_for, flash, session
from flask_pyoidc import OIDCAuthentication
from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata
from google.oauth2 import service_account
from googleapiclient.discovery import build
import os
import json

app = Flask(__name__)
app.secret_key = os.urandom(24)

# 讀取 allowed_email.txt,存成 set
def load_allowed_emails(filepath="allowed_email.txt"):
    with open(filepath, encoding="utf-8") as f:
        return set(line.strip().lower() for line in f if line.strip())

ALLOWED_EMAILS = load_allowed_emails()

# === OIDC 設定 ===
with open('oidc_secrets.json') as f:
    oidc_conf = json.load(f)['web']

issuer = oidc_conf['issuer']
client_metadata = ClientMetadata(
    client_id=oidc_conf['client_id'],
    client_secret=oidc_conf['client_secret'],
    post_logout_redirect_uris=[oidc_conf['redirect_uris'][0]]
)
provider_config = ProviderConfiguration(
    issuer=issuer,
    client_metadata=client_metadata
)
auth = OIDCAuthentication({'default': provider_config}, app)

# === Google Workspace 設定 ===
DOMAIN = "your_google_workspace_domain"
ADMIN_EMAIL = "your_admin_email"
SERVICE_ACCOUNT_FILE = "C:/your_service_account_file_directory/service.json"
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user']

def create_user(email_prefix, password):
    credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)
    delegated_credentials = credentials.with_subject(ADMIN_EMAIL)
    service = build('admin', 'directory_v1', credentials=delegated_credentials)
    user_body = {
        "primaryEmail": f"{email_prefix}@{DOMAIN}",
        "name": {
            "givenName": email_prefix,
            "familyName": "User"
        },
        "password": password
    }
    user = service.users().insert(body=user_body).execute()
    return user

@app.route('/logout')
def logout():
    session.clear()
    return redirect(auth.client_end_session_uri('default', url_for('index', _external=True)))

@app.route('/', methods=['GET', 'POST'])
@auth.oidc_auth('default')
def index():
    user_session = auth.user_session
    user_email = user_session.userinfo['email'].lower()

    # 只允許在 allowed_email.txt 的 email
    if user_email not in ALLOWED_EMAILS:
        return render_template('not_allowed.html', user_email=user_email), 403

    if request.method == 'POST':
        prefix = request.form['prefix']
        account_password = request.form['account_password']
        confirm_password = request.form['confirm_password']

        if not prefix.isdigit():
            flash("請輸入您的學號")
            return redirect(url_for('index'))

        if account_password != confirm_password:
            flash("兩次密碼不一致,請重新確認!")
            return redirect(url_for('index'))

        if len(account_password) < 8:
            flash("密碼長度必須至少 8 個字元!")
            return redirect(url_for('index'))

        try:
            result = create_user(prefix, account_password)
            flash(f"帳號建立成功: {result['primaryEmail']}")
        except Exception as e:
            flash(f"建立帳號失敗: {str(e)}")
        return redirect(url_for('index'))
    return render_template('form.html', user_email=user_email)

templates/form.html

<!doctype html>
<title>建立 Google Workspace 帳號</title>
<h2>建立 Google Workspace 帳號</h2>
<p>目前登入:{{ user_email }}</p>
<form method="POST">
  <label>學號(Email @ 前):<input type="text" name="prefix" required pattern="\d+"></label><br>
  <label>新帳號密碼:<input type="password" name="account_password" required></label><br>
  <label>再次輸入密碼:<input type="password" name="confirm_password" required></label><br>
  <input type="submit" value="送出">
</form>
{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul>
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}
<a href="{{ url_for('logout') }}">登出</a>

另外新增一個 templates/not_allowed.html

<!doctype html>
<title>未授權訪問</title>
<h2>未授權訪問</h2>
<p>您的 email({{ user_email }})不在允許名單中,無法建立帳號。</p>
<a href="{{ url_for('logout') }}">登出</a>


Project << Previous Next >> API

Copyright © All rights reserved | This template is made with by Colorlib