forked from ScoDoc/DocScoDoc
API v9: première esquisse
This commit is contained in:
parent
4ac076ec6c
commit
be224b9576
@ -117,6 +117,7 @@ def create_app(config_class=DevConfig):
|
|||||||
from app.views import notes_bp
|
from app.views import notes_bp
|
||||||
from app.views import users_bp
|
from app.views import users_bp
|
||||||
from app.views import absences_bp
|
from app.views import absences_bp
|
||||||
|
from app.api import bp as api_bp
|
||||||
|
|
||||||
# https://scodoc.fr/ScoDoc
|
# https://scodoc.fr/ScoDoc
|
||||||
app.register_blueprint(scodoc_bp)
|
app.register_blueprint(scodoc_bp)
|
||||||
@ -130,6 +131,7 @@ def create_app(config_class=DevConfig):
|
|||||||
app.register_blueprint(
|
app.register_blueprint(
|
||||||
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
||||||
)
|
)
|
||||||
|
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
||||||
scodoc_exc_formatter = RequestFormatter(
|
scodoc_exc_formatter = RequestFormatter(
|
||||||
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
|
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
|
||||||
"%(levelname)s in %(module)s: %(message)s"
|
"%(levelname)s in %(module)s: %(message)s"
|
||||||
|
8
app/api/__init__.py
Normal file
8
app/api/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
"""api.__init__
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint("api", __name__)
|
||||||
|
|
||||||
|
from app.api import sco_api
|
53
app/api/auth.py
Normal file
53
app/api/auth.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# -*- coding: UTF-8 -*
|
||||||
|
# Authentication code borrowed from Miguel Grinberg's Mega Tutorial
|
||||||
|
# (see https://github.com/miguelgrinberg/microblog)
|
||||||
|
|
||||||
|
# Under The MIT License (MIT)
|
||||||
|
|
||||||
|
# Copyright (c) 2017 Miguel Grinberg
|
||||||
|
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
# this software and associated documentation files (the "Software"), to deal in
|
||||||
|
# the Software without restriction, including without limitation the rights to
|
||||||
|
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
# the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
# subject to the following conditions:
|
||||||
|
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
|
||||||
|
from app.auth.models import User
|
||||||
|
from app.api.errors import error_response
|
||||||
|
|
||||||
|
basic_auth = HTTPBasicAuth()
|
||||||
|
token_auth = HTTPTokenAuth()
|
||||||
|
|
||||||
|
|
||||||
|
@basic_auth.verify_password
|
||||||
|
def verify_password(username, password):
|
||||||
|
user = User.query.filter_by(username=username).first()
|
||||||
|
if user and user.check_password(password):
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@basic_auth.error_handler
|
||||||
|
def basic_auth_error(status):
|
||||||
|
return error_response(status)
|
||||||
|
|
||||||
|
|
||||||
|
@token_auth.verify_token
|
||||||
|
def verify_token(token):
|
||||||
|
return User.check_token(token) if token else None
|
||||||
|
|
||||||
|
|
||||||
|
@token_auth.error_handler
|
||||||
|
def token_auth_error(status):
|
||||||
|
return error_response(status)
|
37
app/api/errors.py
Normal file
37
app/api/errors.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Authentication code borrowed from Miguel Grinberg's Mega Tutorial
|
||||||
|
# (see https://github.com/miguelgrinberg/microblog)
|
||||||
|
|
||||||
|
# Under The MIT License (MIT)
|
||||||
|
|
||||||
|
# Copyright (c) 2017 Miguel Grinberg
|
||||||
|
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
# this software and associated documentation files (the "Software"), to deal in
|
||||||
|
# the Software without restriction, including without limitation the rights to
|
||||||
|
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
# the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
# subject to the following conditions:
|
||||||
|
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.from flask import jsonify
|
||||||
|
from werkzeug.http import HTTP_STATUS_CODES
|
||||||
|
|
||||||
|
|
||||||
|
def error_response(status_code, message=None):
|
||||||
|
payload = {"error": HTTP_STATUS_CODES.get(status_code, "Unknown error")}
|
||||||
|
if message:
|
||||||
|
payload["message"] = message
|
||||||
|
response = jsonify(payload)
|
||||||
|
response.status_code = status_code
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def bad_request(message):
|
||||||
|
return error_response(400, message)
|
46
app/api/sco_api.py
Normal file
46
app/api/sco_api.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gestion scolarite IUT
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""API ScoDoc 9
|
||||||
|
"""
|
||||||
|
# PAS ENCORE IMPLEMENTEE, juste un essai
|
||||||
|
|
||||||
|
from flask import jsonify, request, url_for, abort
|
||||||
|
from app import db
|
||||||
|
from app.api import bp
|
||||||
|
from app.api.auth import token_auth
|
||||||
|
from app.api.errors import bad_request
|
||||||
|
|
||||||
|
from app import models
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/api/list_depts", methods=["GET"])
|
||||||
|
@token_auth.login_required
|
||||||
|
def list_depts():
|
||||||
|
depts = models.Departement.query.filter_by(visible=True).all()
|
||||||
|
data = {"items": [d.to_dict() for d in depts]}
|
||||||
|
return jsonify(data)
|
20
app/api/tokens.py
Normal file
20
app/api/tokens.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from flask import jsonify
|
||||||
|
from app import db
|
||||||
|
from app.api import bp
|
||||||
|
from app.api.auth import basic_auth, token_auth
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/tokens", methods=["POST"])
|
||||||
|
@basic_auth.login_required
|
||||||
|
def get_token():
|
||||||
|
token = basic_auth.current_user().get_token()
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({"token": token})
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/tokens", methods=["DELETE"])
|
||||||
|
@token_auth.login_required
|
||||||
|
def revoke_token():
|
||||||
|
token_auth.current_user().revoke_token()
|
||||||
|
db.session.commit()
|
||||||
|
return "", 204
|
@ -37,9 +37,11 @@ def login():
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user = User.query.filter_by(user_name=form.user_name.data).first()
|
user = User.query.filter_by(user_name=form.user_name.data).first()
|
||||||
if user is None or not user.check_password(form.password.data):
|
if user is None or not user.check_password(form.password.data):
|
||||||
|
current_app.logger.info("login: invalid (%s)", form.user_name.data)
|
||||||
flash(_("Invalid user name or password"))
|
flash(_("Invalid user name or password"))
|
||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
login_user(user, remember=form.remember_me.data)
|
login_user(user, remember=form.remember_me.data)
|
||||||
|
current_app.logger.info("login: success (%s)", form.user_name.data)
|
||||||
next_page = request.args.get("next")
|
next_page = request.args.get("next")
|
||||||
if not next_page or url_parse(next_page).netloc != "":
|
if not next_page or url_parse(next_page).netloc != "":
|
||||||
next_page = url_for("scodoc.index")
|
next_page = url_for("scodoc.index")
|
||||||
|
@ -34,3 +34,13 @@ class Departement(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Departement {self.acronym}>"
|
return f"<Departement {self.acronym}>"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
data = {
|
||||||
|
"id": self.id,
|
||||||
|
"acronym": self.acronym,
|
||||||
|
"description": self.description,
|
||||||
|
"visible": self.visible,
|
||||||
|
"date_creation": self.date_creation,
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
@ -52,6 +52,10 @@ def POST(s, path, data, errmsg=None):
|
|||||||
|
|
||||||
# --- Ouverture session (login)
|
# --- Ouverture session (login)
|
||||||
s = requests.Session()
|
s = requests.Session()
|
||||||
|
s.post(
|
||||||
|
"https://deb11.viennet.net/api/auth/login",
|
||||||
|
data={"user_name": USER, "password": PASSWORD},
|
||||||
|
)
|
||||||
r = s.get(BASEURL, auth=(USER, PASSWORD), verify=CHECK_CERTIFICATE)
|
r = s.get(BASEURL, auth=(USER, PASSWORD), verify=CHECK_CERTIFICATE)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise ScoError("erreur de connection: vérifier adresse et identifiants")
|
raise ScoError("erreur de connection: vérifier adresse et identifiants")
|
||||||
|
@ -18,6 +18,7 @@ Flask==2.0.1
|
|||||||
Flask-Babel==2.0.0
|
Flask-Babel==2.0.0
|
||||||
Flask-Bootstrap==3.3.7.1
|
Flask-Bootstrap==3.3.7.1
|
||||||
Flask-Caching==1.10.1
|
Flask-Caching==1.10.1
|
||||||
|
Flask-HTTPAuth==4.4.0
|
||||||
Flask-Login==0.5.0
|
Flask-Login==0.5.0
|
||||||
Flask-Mail==0.9.1
|
Flask-Mail==0.9.1
|
||||||
Flask-Migrate==3.1.0
|
Flask-Migrate==3.1.0
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.0.11"
|
SCOVERSION = "9.0.12"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user