Compare commits
1 Commits
master
...
moodle-not
Author | SHA1 | Date | |
---|---|---|---|
06d83cc691 |
20
.env-exemple
@ -1,20 +0,0 @@
|
|||||||
# Fichier à configurer et renommer en .env
|
|
||||||
# (dans /opt/scodoc)
|
|
||||||
# Il doit appartenir à (ou être lisible par) "scodoc"
|
|
||||||
|
|
||||||
|
|
||||||
FLASK_APP=scodoc.py
|
|
||||||
FLASK_ENV=production # ou "development" si vous développez
|
|
||||||
|
|
||||||
# Envois de mails: adapter si nécessaire
|
|
||||||
# MAIL_SERVER=localhost
|
|
||||||
|
|
||||||
# Obligatoire:
|
|
||||||
SCODOC_ADMIN_MAIL="emmanuel@viennet.net"
|
|
||||||
|
|
||||||
# Remplacer cette chaine
|
|
||||||
# Vous pouvez utiliser
|
|
||||||
# python3 -c "import uuid; print(uuid.uuid4().hex)"
|
|
||||||
# pour en créer une de ce genre, aléatoire
|
|
||||||
SECRET_KEY="53ffeff44a3940dea4964d628af33dd9"
|
|
||||||
|
|
2
.gitignore
vendored
@ -131,7 +131,6 @@ venv/
|
|||||||
ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
envsco8/
|
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
@ -170,4 +169,3 @@ Thumbs.db
|
|||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
|
||||||
|
|
||||||
copy
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
[[MESSAGES CONTROL]
|
|
||||||
# pylint and black disagree...
|
|
||||||
disable=bad-continuation
|
|
||||||
|
|
||||||
[TYPECHECK]
|
|
||||||
ignored-classes=Permission,SQLObject,Registrant,scoped_session
|
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -25,47 +25,41 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
""" Importation des étudiants à partir de fichiers CSV
|
""" Importation des etudiants à partir de fichiers CSV
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import collections
|
|
||||||
import io
|
|
||||||
import os
|
import os
|
||||||
import re
|
import sys
|
||||||
import time
|
import time
|
||||||
from datetime import date
|
import pdb
|
||||||
|
import collections
|
||||||
|
import types
|
||||||
|
import re
|
||||||
|
|
||||||
from flask import g, url_for
|
import sco_utils as scu
|
||||||
|
import notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
from notes_log import log
|
||||||
import app.scodoc.notesdb as ndb
|
import scolars
|
||||||
from app import log
|
import sco_formsemestre
|
||||||
from app.scodoc.sco_excel import COLORS
|
import sco_groups
|
||||||
from app.scodoc.sco_formsemestre_inscriptions import (
|
import sco_excel
|
||||||
do_formsemestre_inscription_with_modules,
|
import sco_groups_view
|
||||||
)
|
import sco_news
|
||||||
from app.scodoc.gen_tables import GenTable
|
from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC
|
||||||
from app.scodoc.sco_exceptions import (
|
from sco_formsemestre_inscriptions import do_formsemestre_inscription_with_modules
|
||||||
|
from gen_tables import GenTable
|
||||||
|
from sco_exceptions import (
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
ScoFormatError,
|
FormatError,
|
||||||
ScoException,
|
ScoException,
|
||||||
ScoValueError,
|
ScoValueError,
|
||||||
ScoInvalidDateError,
|
ScoInvalidDateError,
|
||||||
ScoLockedFormError,
|
ScoLockedFormError,
|
||||||
ScoGenError,
|
ScoGenError,
|
||||||
)
|
)
|
||||||
from app.scodoc import html_sco_header
|
|
||||||
from app.scodoc import sco_cache
|
|
||||||
from app.scodoc import sco_etud
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc import sco_groups
|
|
||||||
from app.scodoc import sco_excel
|
|
||||||
from app.scodoc import sco_groups_view
|
|
||||||
from app.scodoc import sco_news
|
|
||||||
from app.scodoc import sco_preferences
|
|
||||||
|
|
||||||
# format description (in tools/)
|
# format description (relative to Product directory))
|
||||||
FORMAT_FILE = "format_import_etudiants.txt"
|
FORMAT_FILE = "misc/format_import_etudiants.txt"
|
||||||
|
|
||||||
# Champs modifiables via "Import données admission"
|
# Champs modifiables via "Import données admission"
|
||||||
ADMISSION_MODIFIABLE_FIELDS = (
|
ADMISSION_MODIFIABLE_FIELDS = (
|
||||||
@ -102,6 +96,8 @@ ADMISSION_MODIFIABLE_FIELDS = (
|
|||||||
"paysdomicile",
|
"paysdomicile",
|
||||||
"telephone",
|
"telephone",
|
||||||
"telephonemobile",
|
"telephonemobile",
|
||||||
|
# Debouche
|
||||||
|
"debouche",
|
||||||
# Groupes
|
# Groupes
|
||||||
"groupes",
|
"groupes",
|
||||||
)
|
)
|
||||||
@ -112,7 +108,7 @@ ADMISSION_MODIFIABLE_FIELDS = (
|
|||||||
def sco_import_format(with_codesemestre=True):
|
def sco_import_format(with_codesemestre=True):
|
||||||
"returns tuples (Attribut, Type, Table, AllowNulls, Description)"
|
"returns tuples (Attribut, Type, Table, AllowNulls, Description)"
|
||||||
r = []
|
r = []
|
||||||
for l in open(os.path.join(scu.SCO_TOOLS_DIR, FORMAT_FILE)):
|
for l in open(scu.SCO_SRCDIR + "/" + FORMAT_FILE):
|
||||||
l = l.strip()
|
l = l.strip()
|
||||||
if l and l[0] != "#":
|
if l and l[0] != "#":
|
||||||
fs = l.split(";")
|
fs = l.split(";")
|
||||||
@ -157,6 +153,8 @@ def sco_import_generate_excel_sample(
|
|||||||
exclude_cols=[],
|
exclude_cols=[],
|
||||||
extra_cols=[],
|
extra_cols=[],
|
||||||
group_ids=[],
|
group_ids=[],
|
||||||
|
context=None,
|
||||||
|
REQUEST=None,
|
||||||
):
|
):
|
||||||
"""Generates an excel document based on format fmt
|
"""Generates an excel document based on format fmt
|
||||||
(format is the result of sco_import_format())
|
(format is the result of sco_import_format())
|
||||||
@ -164,15 +162,15 @@ def sco_import_generate_excel_sample(
|
|||||||
(only columns from these tables will be generated)
|
(only columns from these tables will be generated)
|
||||||
If group_ids, liste les etudiants de ces groupes
|
If group_ids, liste les etudiants de ces groupes
|
||||||
"""
|
"""
|
||||||
style = sco_excel.excel_make_style(bold=True)
|
style = sco_excel.Excel_MakeStyle(bold=True)
|
||||||
style_required = sco_excel.excel_make_style(bold=True, color=COLORS.RED)
|
style_required = sco_excel.Excel_MakeStyle(bold=True, color="red")
|
||||||
titles = []
|
titles = []
|
||||||
titlesStyles = []
|
titlesStyles = []
|
||||||
for l in fmt:
|
for l in fmt:
|
||||||
name = l[0].lower()
|
name = scu.strlower(l[0])
|
||||||
if (not with_codesemestre) and name == "codesemestre":
|
if (not with_codesemestre) and name == "codesemestre":
|
||||||
continue # pas de colonne codesemestre
|
continue # pas de colonne codesemestre
|
||||||
if only_tables is not None and l[2].lower() not in only_tables:
|
if only_tables is not None and scu.strlower(l[2]) not in only_tables:
|
||||||
continue # table non demandée
|
continue # table non demandée
|
||||||
if name in exclude_cols:
|
if name in exclude_cols:
|
||||||
continue # colonne exclue
|
continue # colonne exclue
|
||||||
@ -186,8 +184,10 @@ def sco_import_generate_excel_sample(
|
|||||||
titlesStyles.append(style)
|
titlesStyles.append(style)
|
||||||
titles += extra_cols
|
titles += extra_cols
|
||||||
titlesStyles += [style] * len(extra_cols)
|
titlesStyles += [style] * len(extra_cols)
|
||||||
if group_ids:
|
if group_ids and context:
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
|
context, group_ids, REQUEST=REQUEST
|
||||||
|
)
|
||||||
members = groups_infos.members
|
members = groups_infos.members
|
||||||
log(
|
log(
|
||||||
"sco_import_generate_excel_sample: group_ids=%s %d members"
|
"sco_import_generate_excel_sample: group_ids=%s %d members"
|
||||||
@ -198,61 +198,62 @@ def sco_import_generate_excel_sample(
|
|||||||
# rempli table avec données actuelles
|
# rempli table avec données actuelles
|
||||||
lines = []
|
lines = []
|
||||||
for i in members:
|
for i in members:
|
||||||
etud = sco_etud.get_etud_info(etudid=i["etudid"], filled=True)[0]
|
etud = context.getEtudInfo(etudid=i["etudid"], filled=True)[0]
|
||||||
l = []
|
l = []
|
||||||
for field in titles:
|
for field in titles:
|
||||||
if field == "groupes":
|
if field == "groupes":
|
||||||
sco_groups.etud_add_group_infos(
|
sco_groups.etud_add_group_infos(
|
||||||
etud, groups_infos.formsemestre, sep=";"
|
context, etud, groups_infos.formsemestre, sep=";"
|
||||||
)
|
)
|
||||||
l.append(etud["partitionsgroupes"])
|
l.append(etud["partitionsgroupes"])
|
||||||
else:
|
else:
|
||||||
key = field.lower().split()[0]
|
key = scu.strlower(field).split()[0]
|
||||||
l.append(etud.get(key, ""))
|
l.append(etud.get(key, ""))
|
||||||
lines.append(l)
|
lines.append(l)
|
||||||
else:
|
else:
|
||||||
lines = [[]] # empty content, titles only
|
lines = [[]] # empty content, titles only
|
||||||
return sco_excel.excel_simple_table(
|
return sco_excel.Excel_SimpleTable(
|
||||||
titles=titles, titles_styles=titlesStyles, sheet_name="Etudiants", lines=lines
|
titles=titles, titlesStyles=titlesStyles, SheetName="Etudiants", lines=lines
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def students_import_excel(
|
def students_import_excel(
|
||||||
|
context,
|
||||||
csvfile,
|
csvfile,
|
||||||
|
REQUEST=None,
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
check_homonyms=True,
|
check_homonyms=True,
|
||||||
require_ine=False,
|
require_ine=False,
|
||||||
return_html=True,
|
|
||||||
):
|
):
|
||||||
"import students from Excel file"
|
"import students from Excel file"
|
||||||
diag = scolars_import_excel_file(
|
diag = scolars_import_excel_file(
|
||||||
csvfile,
|
csvfile,
|
||||||
|
context.Notes,
|
||||||
|
REQUEST,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
check_homonyms=check_homonyms,
|
check_homonyms=check_homonyms,
|
||||||
require_ine=require_ine,
|
require_ine=require_ine,
|
||||||
exclude_cols=["photo_filename"],
|
exclude_cols=["photo_filename"],
|
||||||
)
|
)
|
||||||
if return_html:
|
if REQUEST:
|
||||||
if formsemestre_id:
|
if formsemestre_id:
|
||||||
dest = url_for(
|
dest = "formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
||||||
"notes.formsemestre_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
dest = url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
|
dest = context.NotesURL()
|
||||||
H = [html_sco_header.sco_header(page_title="Import etudiants")]
|
H = [context.sco_header(REQUEST, page_title="Import etudiants")]
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
for d in diag:
|
for d in diag:
|
||||||
H.append("<li>%s</li>" % d)
|
H.append("<li>%s</li>" % d)
|
||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
H.append("<p>Import terminé !</p>")
|
H.append("<p>Import terminé !</p>")
|
||||||
H.append('<p><a class="stdlink" href="%s">Continuer</a></p>' % dest)
|
H.append('<p><a class="stdlink" href="%s">Continuer</a></p>' % dest)
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + context.sco_footer(REQUEST)
|
||||||
|
|
||||||
|
|
||||||
def scolars_import_excel_file(
|
def scolars_import_excel_file(
|
||||||
datafile: io.BytesIO,
|
datafile,
|
||||||
|
context,
|
||||||
|
REQUEST,
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
check_homonyms=True,
|
check_homonyms=True,
|
||||||
require_ine=False,
|
require_ine=False,
|
||||||
@ -262,23 +263,24 @@ def scolars_import_excel_file(
|
|||||||
et les inscrit dans le semestre indiqué (et à TOUS ses modules)
|
et les inscrit dans le semestre indiqué (et à TOUS ses modules)
|
||||||
"""
|
"""
|
||||||
log("scolars_import_excel_file: formsemestre_id=%s" % formsemestre_id)
|
log("scolars_import_excel_file: formsemestre_id=%s" % formsemestre_id)
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = context.GetDBConnexion(autocommit=False)
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
annee_courante = time.localtime()[0]
|
annee_courante = time.localtime()[0]
|
||||||
always_require_ine = sco_preferences.get_preference("always_require_ine")
|
always_require_ine = context.get_preference("always_require_ine")
|
||||||
exceldata = datafile.read()
|
exceldata = datafile.read()
|
||||||
if not exceldata:
|
if not exceldata:
|
||||||
raise ScoValueError("Ficher excel vide ou invalide")
|
raise ScoValueError("Ficher excel vide ou invalide")
|
||||||
diag, data = sco_excel.excel_bytes_to_list(exceldata)
|
diag, data = sco_excel.Excel_to_list(exceldata)
|
||||||
if not data: # probably a bug
|
if not data: # probably a bug
|
||||||
raise ScoException("scolars_import_excel_file: empty file !")
|
raise ScoException("scolars_import_excel_file: empty file !")
|
||||||
|
|
||||||
formsemestre_to_invalidate = set()
|
formsemestre_to_invalidate = set()
|
||||||
|
|
||||||
# 1- --- check title line
|
# 1- --- check title line
|
||||||
titles = {}
|
titles = {}
|
||||||
fmt = sco_import_format()
|
fmt = sco_import_format()
|
||||||
for l in fmt:
|
for l in fmt:
|
||||||
tit = l[0].lower().split()[0] # titles in lowercase, and take 1st word
|
tit = scu.strlower(l[0]).split()[0] # titles in lowercase, and take 1st word
|
||||||
if (
|
if (
|
||||||
(not formsemestre_id) or (tit != "codesemestre")
|
(not formsemestre_id) or (tit != "codesemestre")
|
||||||
) and tit not in exclude_cols:
|
) and tit not in exclude_cols:
|
||||||
@ -287,27 +289,27 @@ def scolars_import_excel_file(
|
|||||||
# log("titles=%s" % titles)
|
# log("titles=%s" % titles)
|
||||||
# remove quotes, downcase and keep only 1st word
|
# remove quotes, downcase and keep only 1st word
|
||||||
try:
|
try:
|
||||||
fs = [scu.stripquotes(s).lower().split()[0] for s in data[0]]
|
fs = [scu.strlower(scu.stripquotes(s)).split()[0] for s in data[0]]
|
||||||
except:
|
except:
|
||||||
raise ScoValueError("Titres de colonnes invalides (ou vides ?)")
|
raise ScoValueError("Titres de colonnes invalides (ou vides ?)")
|
||||||
# log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
# log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
||||||
|
|
||||||
# check columns titles
|
# check columns titles
|
||||||
if len(fs) != len(titles):
|
if len(fs) != len(titles):
|
||||||
missing = {}.fromkeys(list(titles.keys()))
|
missing = {}.fromkeys(titles.keys())
|
||||||
unknown = []
|
unknown = []
|
||||||
for f in fs:
|
for f in fs:
|
||||||
if f in missing:
|
if missing.has_key(f):
|
||||||
del missing[f]
|
del missing[f]
|
||||||
else:
|
else:
|
||||||
unknown.append(f)
|
unknown.append(f)
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Nombre de colonnes incorrect (devrait être %d, et non %d) <br/> (colonnes manquantes: %s, colonnes invalides: %s)"
|
"Nombre de colonnes incorrect (devrait être %d, et non %d) <br/> (colonnes manquantes: %s, colonnes invalides: %s)"
|
||||||
% (len(titles), len(fs), list(missing.keys()), unknown)
|
% (len(titles), len(fs), missing.keys(), unknown)
|
||||||
)
|
)
|
||||||
titleslist = []
|
titleslist = []
|
||||||
for t in fs:
|
for t in fs:
|
||||||
if t not in titles:
|
if not titles.has_key(t):
|
||||||
raise ScoValueError('Colonne invalide: "%s"' % t)
|
raise ScoValueError('Colonne invalide: "%s"' % t)
|
||||||
titleslist.append(t) #
|
titleslist.append(t) #
|
||||||
# ok, same titles
|
# ok, same titles
|
||||||
@ -364,26 +366,22 @@ def scolars_import_excel_file(
|
|||||||
% (val, linenum, titleslist[i])
|
% (val, linenum, titleslist[i])
|
||||||
)
|
)
|
||||||
# xxx Ad-hoc checks (should be in format description)
|
# xxx Ad-hoc checks (should be in format description)
|
||||||
if titleslist[i].lower() == "sexe":
|
if scu.strlower(titleslist[i]) == "sexe":
|
||||||
try:
|
try:
|
||||||
val = sco_etud.input_civilite(val)
|
val = scolars.input_civilite(val)
|
||||||
except:
|
except:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"valeur invalide pour 'SEXE' (doit etre 'M', 'F', ou 'MME', 'H', 'X' ou vide, mais pas '%s') ligne %d, colonne %s"
|
"valeur invalide pour 'SEXE' (doit etre 'M', 'F', ou 'MME', 'H', 'X' ou vide, mais pas '%s') ligne %d, colonne %s"
|
||||||
% (val, linenum, titleslist[i])
|
% (val, linenum, titleslist[i])
|
||||||
)
|
)
|
||||||
# Excel date conversion:
|
# Excel date conversion:
|
||||||
if titleslist[i].lower() == "date_naissance":
|
if scu.strlower(titleslist[i]) == "date_naissance":
|
||||||
if val:
|
if val:
|
||||||
try:
|
if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
|
||||||
val = sco_excel.xldate_as_datetime(val)
|
val = sco_excel.xldate_as_datetime(float(val))
|
||||||
except ValueError:
|
|
||||||
raise ScoValueError(
|
|
||||||
f"date invalide ({val}) sur ligne {linenum}, colonne {titleslist[i]}"
|
|
||||||
)
|
|
||||||
# INE
|
# INE
|
||||||
if (
|
if (
|
||||||
titleslist[i].lower() == "code_ine"
|
scu.strlower(titleslist[i]) == "code_ine"
|
||||||
and always_require_ine
|
and always_require_ine
|
||||||
and not val
|
and not val
|
||||||
):
|
):
|
||||||
@ -404,7 +402,7 @@ def scolars_import_excel_file(
|
|||||||
if values["code_ine"] and not is_new_ine:
|
if values["code_ine"] and not is_new_ine:
|
||||||
raise ScoValueError("Code INE dupliqué (%s)" % values["code_ine"])
|
raise ScoValueError("Code INE dupliqué (%s)" % values["code_ine"])
|
||||||
# Check nom/prenom
|
# Check nom/prenom
|
||||||
ok, NbHomonyms = sco_etud.check_nom_prenom(
|
ok, NbHomonyms = scolars.check_nom_prenom(
|
||||||
cnx, nom=values["nom"], prenom=values["prenom"]
|
cnx, nom=values["nom"], prenom=values["prenom"]
|
||||||
)
|
)
|
||||||
if not ok:
|
if not ok:
|
||||||
@ -414,8 +412,11 @@ def scolars_import_excel_file(
|
|||||||
if NbHomonyms:
|
if NbHomonyms:
|
||||||
NbImportedHomonyms += 1
|
NbImportedHomonyms += 1
|
||||||
# Insert in DB tables
|
# Insert in DB tables
|
||||||
formsemestre_id_etud = _import_one_student(
|
formsemestre_to_invalidate.add(
|
||||||
|
_import_one_student(
|
||||||
|
context,
|
||||||
cnx,
|
cnx,
|
||||||
|
REQUEST,
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
values,
|
values,
|
||||||
GroupIdInferers,
|
GroupIdInferers,
|
||||||
@ -423,6 +424,7 @@ def scolars_import_excel_file(
|
|||||||
created_etudids,
|
created_etudids,
|
||||||
linenum,
|
linenum,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Verification proportion d'homonymes: si > 10%, abandonne
|
# Verification proportion d'homonymes: si > 10%, abandonne
|
||||||
log("scolars_import_excel_file: detected %d homonyms" % NbImportedHomonyms)
|
log("scolars_import_excel_file: detected %d homonyms" % NbImportedHomonyms)
|
||||||
@ -461,7 +463,7 @@ def scolars_import_excel_file(
|
|||||||
{"etudid": etudid},
|
{"etudid": etudid},
|
||||||
)
|
)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"delete from identite where id=%(etudid)s", {"etudid": etudid}
|
"delete from identite where etudid=%(etudid)s", {"etudid": etudid}
|
||||||
)
|
)
|
||||||
cnx.commit()
|
cnx.commit()
|
||||||
log("scolars_import_excel_file: re-raising exception")
|
log("scolars_import_excel_file: re-raising exception")
|
||||||
@ -470,7 +472,9 @@ def scolars_import_excel_file(
|
|||||||
diag.append("Import et inscription de %s étudiants" % len(created_etudids))
|
diag.append("Import et inscription de %s étudiants" % len(created_etudids))
|
||||||
|
|
||||||
sco_news.add(
|
sco_news.add(
|
||||||
typ=sco_news.NEWS_INSCR,
|
context,
|
||||||
|
REQUEST,
|
||||||
|
typ=NEWS_INSCR,
|
||||||
text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents
|
text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents
|
||||||
% len(created_etudids),
|
% len(created_etudids),
|
||||||
object=formsemestre_id,
|
object=formsemestre_id,
|
||||||
@ -480,47 +484,22 @@ def scolars_import_excel_file(
|
|||||||
cnx.commit()
|
cnx.commit()
|
||||||
|
|
||||||
# Invalide les caches des semestres dans lesquels on a inscrit des etudiants:
|
# Invalide les caches des semestres dans lesquels on a inscrit des etudiants:
|
||||||
for formsemestre_id in formsemestre_to_invalidate:
|
context.Notes._inval_cache(formsemestre_id_list=formsemestre_to_invalidate)
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
|
||||||
|
|
||||||
return diag
|
return diag
|
||||||
|
|
||||||
|
|
||||||
def students_import_admission(
|
|
||||||
csvfile, type_admission="", formsemestre_id=None, return_html=True
|
|
||||||
):
|
|
||||||
"import donnees admission from Excel file (v2016)"
|
|
||||||
diag = scolars_import_admission(
|
|
||||||
csvfile,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
type_admission=type_admission,
|
|
||||||
)
|
|
||||||
if return_html:
|
|
||||||
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
|
||||||
H.append("<p>Import terminé !</p>")
|
|
||||||
H.append(
|
|
||||||
'<p><a class="stdlink" href="%s">Continuer</a></p>'
|
|
||||||
% url_for(
|
|
||||||
"notes.formsemestre_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if diag:
|
|
||||||
H.append("<p>Diagnostic: <ul><li>%s</li></ul></p>" % "</li><li>".join(diag))
|
|
||||||
|
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
|
||||||
|
|
||||||
|
|
||||||
def _import_one_student(
|
def _import_one_student(
|
||||||
|
context,
|
||||||
cnx,
|
cnx,
|
||||||
|
REQUEST,
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
values,
|
values,
|
||||||
GroupIdInferers,
|
GroupIdInferers,
|
||||||
annee_courante,
|
annee_courante,
|
||||||
created_etudids,
|
created_etudids,
|
||||||
linenum,
|
linenum,
|
||||||
) -> int:
|
):
|
||||||
"""
|
"""
|
||||||
Import d'un étudiant et inscription dans le semestre.
|
Import d'un étudiant et inscription dans le semestre.
|
||||||
Return: id du semestre dans lequel il a été inscrit.
|
Return: id du semestre dans lequel il a été inscrit.
|
||||||
@ -531,16 +510,16 @@ def _import_one_student(
|
|||||||
)
|
)
|
||||||
# Identite
|
# Identite
|
||||||
args = values.copy()
|
args = values.copy()
|
||||||
etudid = sco_etud.identite_create(cnx, args)
|
etudid = scolars.identite_create(cnx, args, context=context, REQUEST=REQUEST)
|
||||||
created_etudids.append(etudid)
|
created_etudids.append(etudid)
|
||||||
# Admissions
|
# Admissions
|
||||||
args["etudid"] = etudid
|
args["etudid"] = etudid
|
||||||
args["annee"] = annee_courante
|
args["annee"] = annee_courante
|
||||||
_ = sco_etud.admission_create(cnx, args)
|
_ = scolars.admission_create(cnx, args)
|
||||||
# Adresse
|
# Adresse
|
||||||
args["typeadresse"] = "domicile"
|
args["typeadresse"] = "domicile"
|
||||||
args["description"] = "(infos admission)"
|
args["description"] = "(infos admission)"
|
||||||
_ = sco_etud.adresse_create(cnx, args)
|
_ = scolars.adresse_create(cnx, args)
|
||||||
# Inscription au semestre
|
# Inscription au semestre
|
||||||
args["etat"] = "I" # etat insc. semestre
|
args["etat"] = "I" # etat insc. semestre
|
||||||
if formsemestre_id:
|
if formsemestre_id:
|
||||||
@ -548,32 +527,30 @@ def _import_one_student(
|
|||||||
else:
|
else:
|
||||||
args["formsemestre_id"] = values["codesemestre"]
|
args["formsemestre_id"] = values["codesemestre"]
|
||||||
formsemestre_id = values["codesemestre"]
|
formsemestre_id = values["codesemestre"]
|
||||||
try:
|
|
||||||
formsemestre_id = int(formsemestre_id)
|
|
||||||
except (ValueError, TypeError) as exc:
|
|
||||||
raise ScoValueError(
|
|
||||||
f"valeur invalide ou manquante dans la colonne codesemestre, ligne {linenum+1}"
|
|
||||||
) from exc
|
|
||||||
# recupere liste des groupes:
|
# recupere liste des groupes:
|
||||||
if formsemestre_id not in GroupIdInferers:
|
if formsemestre_id not in GroupIdInferers:
|
||||||
GroupIdInferers[formsemestre_id] = sco_groups.GroupIdInferer(formsemestre_id)
|
GroupIdInferers[formsemestre_id] = sco_groups.GroupIdInferer(
|
||||||
|
context, formsemestre_id
|
||||||
|
)
|
||||||
gi = GroupIdInferers[formsemestre_id]
|
gi = GroupIdInferers[formsemestre_id]
|
||||||
if args["groupes"]:
|
if args["groupes"]:
|
||||||
groupes = args["groupes"].split(";")
|
groupes = args["groupes"].split(";")
|
||||||
else:
|
else:
|
||||||
groupes = []
|
groupes = []
|
||||||
group_ids = [gi[group_name] for group_name in groupes]
|
group_ids = [gi[group_name] for group_name in groupes]
|
||||||
group_ids = list({}.fromkeys(group_ids).keys()) # uniq
|
group_ids = {}.fromkeys(group_ids).keys() # uniq
|
||||||
if None in group_ids:
|
if None in group_ids:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"groupe invalide sur la ligne %d (groupe %s)" % (linenum, groupes)
|
"groupe invalide sur la ligne %d (groupe %s)" % (linenum, groupes)
|
||||||
)
|
)
|
||||||
|
|
||||||
do_formsemestre_inscription_with_modules(
|
do_formsemestre_inscription_with_modules(
|
||||||
int(args["formsemestre_id"]),
|
context,
|
||||||
|
args["formsemestre_id"],
|
||||||
etudid,
|
etudid,
|
||||||
group_ids,
|
group_ids,
|
||||||
etat="I",
|
etat="I",
|
||||||
|
REQUEST=REQUEST,
|
||||||
method="import_csv_file",
|
method="import_csv_file",
|
||||||
)
|
)
|
||||||
return args["formsemestre_id"]
|
return args["formsemestre_id"]
|
||||||
@ -581,12 +558,14 @@ def _import_one_student(
|
|||||||
|
|
||||||
def _is_new_ine(cnx, code_ine):
|
def _is_new_ine(cnx, code_ine):
|
||||||
"True if this code is not in DB"
|
"True if this code is not in DB"
|
||||||
etuds = sco_etud.identite_list(cnx, {"code_ine": code_ine})
|
etuds = scolars.identite_list(cnx, {"code_ine": code_ine})
|
||||||
return not etuds
|
return not etuds
|
||||||
|
|
||||||
|
|
||||||
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
|
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
|
||||||
def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None):
|
def scolars_import_admission(
|
||||||
|
datafile, context, REQUEST, formsemestre_id=None, type_admission=None
|
||||||
|
):
|
||||||
"""Importe données admission depuis un fichier Excel quelconque
|
"""Importe données admission depuis un fichier Excel quelconque
|
||||||
par exemple ceux utilisés avec APB
|
par exemple ceux utilisés avec APB
|
||||||
|
|
||||||
@ -608,7 +587,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
|
|
||||||
log("scolars_import_admission: formsemestre_id=%s" % formsemestre_id)
|
log("scolars_import_admission: formsemestre_id=%s" % formsemestre_id)
|
||||||
members = sco_groups.get_group_members(
|
members = sco_groups.get_group_members(
|
||||||
sco_groups.get_default_group(formsemestre_id)
|
context, sco_groups.get_default_group(context, formsemestre_id)
|
||||||
)
|
)
|
||||||
etuds_by_nomprenom = {} # { nomprenom : etud }
|
etuds_by_nomprenom = {} # { nomprenom : etud }
|
||||||
diag = []
|
diag = []
|
||||||
@ -621,11 +600,11 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
etuds_by_nomprenom[np] = m
|
etuds_by_nomprenom[np] = m
|
||||||
|
|
||||||
exceldata = datafile.read()
|
exceldata = datafile.read()
|
||||||
diag2, data = sco_excel.excel_bytes_to_list(exceldata)
|
diag2, data = sco_excel.Excel_to_list(exceldata, convert_to_string=False)
|
||||||
if not data:
|
if not data:
|
||||||
raise ScoException("scolars_import_admission: empty file !")
|
raise ScoException("scolars_import_admission: empty file !")
|
||||||
diag += diag2
|
diag += diag2
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = context.GetDBConnexion()
|
||||||
|
|
||||||
titles = data[0]
|
titles = data[0]
|
||||||
# idx -> ('field', convertor)
|
# idx -> ('field', convertor)
|
||||||
@ -640,13 +619,10 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
if (idx_nom is None) or (idx_prenom is None):
|
if (idx_nom is None) or (idx_prenom is None):
|
||||||
log("fields indices=" + ", ".join([str(x) for x in fields]))
|
log("fields indices=" + ", ".join([str(x) for x in fields]))
|
||||||
log("fields titles =" + ", ".join([fields[x][0] for x in fields]))
|
log("fields titles =" + ", ".join([fields[x][0] for x in fields]))
|
||||||
raise ScoFormatError(
|
raise FormatError(
|
||||||
"scolars_import_admission: colonnes nom et prenom requises",
|
"scolars_import_admission: colonnes nom et prenom requises",
|
||||||
dest_url=url_for(
|
dest_url="form_students_import_infos_admissions?formsemestre_id=%s"
|
||||||
"scolar.form_students_import_infos_admissions",
|
% formsemestre_id,
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
modifiable_fields = set(ADMISSION_MODIFIABLE_FIELDS)
|
modifiable_fields = set(ADMISSION_MODIFIABLE_FIELDS)
|
||||||
@ -663,7 +639,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
etud = etuds_by_nomprenom[(nom, prenom)]
|
etud = etuds_by_nomprenom[(nom, prenom)]
|
||||||
cur_adm = sco_etud.admission_list(cnx, args={"etudid": etud["etudid"]})[0]
|
cur_adm = scolars.admission_list(cnx, args={"etudid": etud["etudid"]})[0]
|
||||||
# peuple les champs presents dans le tableau
|
# peuple les champs presents dans le tableau
|
||||||
args = {}
|
args = {}
|
||||||
for idx in fields:
|
for idx in fields:
|
||||||
@ -672,14 +648,11 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
try:
|
try:
|
||||||
val = convertor(line[idx])
|
val = convertor(line[idx])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ScoFormatError(
|
raise FormatError(
|
||||||
'scolars_import_admission: valeur invalide, ligne %d colonne %s: "%s"'
|
'scolars_import_admission: valeur invalide, ligne %d colonne %s: "%s"'
|
||||||
% (nline, field_name, line[idx]),
|
% (nline, field_name, line[idx]),
|
||||||
dest_url=url_for(
|
dest_url="form_students_import_infos_admissions?formsemestre_id=%s"
|
||||||
"scolar.form_students_import_infos_admissions",
|
% formsemestre_id,
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
if val is not None: # note: ne peut jamais supprimer une valeur
|
if val is not None: # note: ne peut jamais supprimer une valeur
|
||||||
args[field_name] = val
|
args[field_name] = val
|
||||||
@ -689,24 +662,24 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
# Type admission: traitement particulier
|
# Type admission: traitement particulier
|
||||||
if not cur_adm["type_admission"] and not args.get("type_admission"):
|
if not cur_adm["type_admission"] and not args.get("type_admission"):
|
||||||
args["type_admission"] = type_admission
|
args["type_admission"] = type_admission
|
||||||
sco_etud.etudident_edit(cnx, args, disable_notify=True)
|
scolars.etudident_edit(cnx, args)
|
||||||
adr = sco_etud.adresse_list(cnx, args={"etudid": etud["etudid"]})
|
adr = scolars.adresse_list(cnx, args={"etudid": etud["etudid"]})
|
||||||
if adr:
|
if adr:
|
||||||
args["adresse_id"] = adr[0]["adresse_id"]
|
args["adresse_id"] = adr[0]["adresse_id"]
|
||||||
sco_etud.adresse_edit(
|
scolars.adresse_edit(
|
||||||
cnx, args, disable_notify=True
|
cnx, args
|
||||||
) # pas de notification ici
|
) # ne passe pas le contexte: pas de notification ici
|
||||||
else:
|
else:
|
||||||
args["typeadresse"] = "domicile"
|
args["typeadresse"] = "domicile"
|
||||||
args["description"] = "(infos admission)"
|
args["description"] = "(infos admission)"
|
||||||
adresse_id = sco_etud.adresse_create(cnx, args)
|
adresse_id = scolars.adresse_create(cnx, args)
|
||||||
# log('import_adm: %s' % args )
|
# log('import_adm: %s' % args )
|
||||||
# Change les groupes si nécessaire:
|
# Change les groupes si nécessaire:
|
||||||
if args["groupes"]:
|
if args["groupes"]:
|
||||||
gi = sco_groups.GroupIdInferer(formsemestre_id)
|
gi = sco_groups.GroupIdInferer(context, formsemestre_id)
|
||||||
groupes = args["groupes"].split(";")
|
groupes = args["groupes"].split(";")
|
||||||
group_ids = [gi[group_name] for group_name in groupes]
|
group_ids = [gi[group_name] for group_name in groupes]
|
||||||
group_ids = list({}.fromkeys(group_ids).keys()) # uniq
|
group_ids = {}.fromkeys(group_ids).keys() # uniq
|
||||||
if None in group_ids:
|
if None in group_ids:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"groupe invalide sur la ligne %d (groupe %s)"
|
"groupe invalide sur la ligne %d (groupe %s)"
|
||||||
@ -715,7 +688,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
|
|
||||||
for group_id in group_ids:
|
for group_id in group_ids:
|
||||||
sco_groups.change_etud_group_in_partition(
|
sco_groups.change_etud_group_in_partition(
|
||||||
args["etudid"], group_id
|
context, args["etudid"], group_id, REQUEST=REQUEST
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
diag.append("import de %s" % (etud["nomprenom"]))
|
diag.append("import de %s" % (etud["nomprenom"]))
|
||||||
@ -723,7 +696,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
nline += 1
|
nline += 1
|
||||||
diag.append("%d lignes importées" % n_import)
|
diag.append("%d lignes importées" % n_import)
|
||||||
if n_import > 0:
|
if n_import > 0:
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
context._inval_cache(formsemestre_id=formsemestre_id)
|
||||||
return diag
|
return diag
|
||||||
|
|
||||||
|
|
||||||
@ -731,7 +704,7 @@ _ADM_PATTERN = re.compile(r"[\W]+", re.UNICODE) # supprime tout sauf alphanum
|
|||||||
|
|
||||||
|
|
||||||
def adm_normalize_string(s): # normalize unicode title
|
def adm_normalize_string(s): # normalize unicode title
|
||||||
return scu.suppress_accents(_ADM_PATTERN.sub("", s.strip().lower())).replace(
|
return scu.suppression_diacritics(_ADM_PATTERN.sub("", s.strip().lower())).replace(
|
||||||
"_", ""
|
"_", ""
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -758,14 +731,11 @@ def adm_get_fields(titles, formsemestre_id):
|
|||||||
convertor = adm_convert_text
|
convertor = adm_convert_text
|
||||||
# doublons ?
|
# doublons ?
|
||||||
if k in [x[0] for x in fields.values()]:
|
if k in [x[0] for x in fields.values()]:
|
||||||
raise ScoFormatError(
|
raise FormatError(
|
||||||
'scolars_import_admission: titre "%s" en double (ligne 1)'
|
'scolars_import_admission: titre "%s" en double (ligne 1)'
|
||||||
% (title),
|
% (title),
|
||||||
dest_url=url_for(
|
dest_url="form_students_import_infos_admissions_apb?formsemestre_id=%s"
|
||||||
"scolar.form_students_import_infos_admissions_apb",
|
% formsemestre_id,
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
fields[idx] = (k, convertor)
|
fields[idx] = (k, convertor)
|
||||||
idx += 1
|
idx += 1
|
||||||
@ -774,24 +744,24 @@ def adm_get_fields(titles, formsemestre_id):
|
|||||||
|
|
||||||
|
|
||||||
def adm_convert_text(v):
|
def adm_convert_text(v):
|
||||||
if isinstance(v, float):
|
if type(v) == types.FloatType:
|
||||||
return "{:g}".format(v) # evite "1.0"
|
return "{:g}".format(v) # evite "1.0"
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
def adm_convert_int(v):
|
def adm_convert_int(v):
|
||||||
if type(v) != int and not v:
|
if type(v) != types.IntType and not v:
|
||||||
return None
|
return None
|
||||||
return int(float(v)) # accept "10.0"
|
return int(float(v)) # accept "10.0"
|
||||||
|
|
||||||
|
|
||||||
def adm_convert_real(v):
|
def adm_convert_real(v):
|
||||||
if type(v) != float and not v:
|
if type(v) != types.FloatType and not v:
|
||||||
return None
|
return None
|
||||||
return float(v)
|
return float(v)
|
||||||
|
|
||||||
|
|
||||||
def adm_table_description_format():
|
def adm_table_description_format(context):
|
||||||
"""Table HTML (ou autre format) decrivant les donnees d'admissions importables"""
|
"""Table HTML (ou autre format) decrivant les donnees d'admissions importables"""
|
||||||
Fmt = sco_import_format_dict(with_codesemestre=False)
|
Fmt = sco_import_format_dict(with_codesemestre=False)
|
||||||
for k in Fmt:
|
for k in Fmt:
|
||||||
@ -816,9 +786,9 @@ def adm_table_description_format():
|
|||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles=titles,
|
titles=titles,
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
rows=list(Fmt.values()),
|
rows=Fmt.values(),
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=context.get_preferences(),
|
||||||
)
|
)
|
||||||
return tab
|
return tab
|
179
README.md
@ -1,189 +1,20 @@
|
|||||||
|
|
||||||
# ScoDoc - Gestion de la scolarité - Version ScoDoc 9
|
# SCODOC - gestion de la scolarité
|
||||||
|
|
||||||
(c) Emmanuel Viennet 1999 - 2022 (voir LICENCE.txt)
|
(c) Emmanuel Viennet 1999 - 2021 (voir LICENCE.txt)
|
||||||
|
|
||||||
Installation: voir instructions à jour sur <https://scodoc.org/GuideInstallDebian11>
|
|
||||||
|
Installation: voir instructions à jour sur <https://scodoc.org>
|
||||||
|
|
||||||
Documentation utilisateur: <https://scodoc.org>
|
Documentation utilisateur: <https://scodoc.org>
|
||||||
|
|
||||||
## Version ScoDoc 9
|
Ce logiciel est un produit pour Zope 2.13 écrit en Python (2.4, passé à 2.7 pour ScoDoc7).
|
||||||
|
|
||||||
La version ScoDoc 9 est basée sur Flask (au lieu de Zope) et sur
|
|
||||||
**python 3.9+**.
|
|
||||||
|
|
||||||
La version 9.0 s'efforce de reproduire presque à l'identique le fonctionnement
|
|
||||||
de ScoDoc7, avec des composants logiciels différents (Debian 11, Python 3,
|
|
||||||
Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### État actuel (4 dec 21)
|
|
||||||
|
|
||||||
- 9.0 (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
|
||||||
- ancien module "Entreprises" (obsolète)
|
|
||||||
|
|
||||||
- 9.1 (branche "PNBUT") est la version de développement.
|
|
||||||
|
|
||||||
|
|
||||||
### Lignes de commandes
|
|
||||||
|
|
||||||
Voir [https://scodoc.org/GuideConfig](le guide de configuration).
|
|
||||||
|
|
||||||
|
|
||||||
## Organisation des fichiers
|
|
||||||
|
|
||||||
L'installation comporte les fichiers de l'application, sous `/opt/scodoc/`, et
|
|
||||||
les fichiers locaux (archives, photos, configurations, logs) sous
|
|
||||||
`/opt/scodoc-data`. Par ailleurs, il y a évidemment les bases de données
|
|
||||||
postgresql et la configuration du système Linux.
|
|
||||||
|
|
||||||
### Fichiers locaux
|
|
||||||
Sous `/opt/scodoc-data`, fichiers et répertoires appartenant à l'utilisateur `scodoc`.
|
|
||||||
Ils ne doivent pas être modifiés à la main, sauf certains fichiers de configuration sous
|
|
||||||
`/opt/scodoc-data/config`.
|
|
||||||
|
|
||||||
Le répertoire `/opt/scodoc-data` doit être régulièrement sauvegardé.
|
|
||||||
|
|
||||||
Principaux contenus:
|
|
||||||
|
|
||||||
/opt/scodoc-data
|
|
||||||
/opt/scodoc-data/log # Fichiers de log ScoDoc
|
|
||||||
/opt/scodoc-data/config # Fichiers de configuration
|
|
||||||
.../config/logos # Logos de l'établissement
|
|
||||||
.../config/depts # un fichier par département
|
|
||||||
/opt/scodoc-data/photos # Photos des étudiants
|
|
||||||
/opt/scodoc-data/archives # Archives: PV de jury, maquettes Apogée, fichiers étudiants
|
|
||||||
|
|
||||||
## Pour les développeurs
|
|
||||||
|
|
||||||
### Installation du code
|
|
||||||
|
|
||||||
Installer ScoDoc 9 normalement ([voir la doc](https://scodoc.org/GuideInstallDebian11)).
|
|
||||||
|
|
||||||
Puis remplacer `/opt/scodoc` par un clone du git.
|
|
||||||
|
|
||||||
sudo su
|
|
||||||
mv /opt/scodoc /opt/off-scodoc # ou ce que vous voulez
|
|
||||||
apt-get install git # si besoin
|
|
||||||
cd /opt
|
|
||||||
git clone https://scodoc.org/git/viennet/ScoDoc.git
|
|
||||||
# (ou bien utiliser votre clone gitea si vous l'avez déjà créé !)
|
|
||||||
mv ScoDoc scodoc # important !
|
|
||||||
|
|
||||||
Il faut ensuite installer l'environnement et le fichier de configuration:
|
|
||||||
|
|
||||||
# Le plus simple est de piquer le virtualenv configuré par l'installeur:
|
|
||||||
mv /opt/off-scodoc/venv /opt/scodoc
|
|
||||||
|
|
||||||
Et la config:
|
|
||||||
|
|
||||||
ln -s /opt/scodoc-data/.env /opt/scodoc
|
|
||||||
|
|
||||||
Cette dernière commande utilise le `.env` crée lors de l'install, ce qui
|
|
||||||
n'est pas toujours le plus judicieux: vous pouvez modifier son contenu, par
|
|
||||||
exemple pour travailler en mode "développement" avec `FLASK_ENV=development`.
|
|
||||||
|
|
||||||
### Tests unitaires
|
|
||||||
|
|
||||||
Les tests unitaires utilisent normalement la base postgresql `SCODOC_TEST`.
|
|
||||||
Avant le premier lancement, créer cette base ainsi:
|
|
||||||
|
|
||||||
./tools/create_database.sh SCODOC_TEST
|
|
||||||
export FLASK_ENV=test
|
|
||||||
flask db upgrade
|
|
||||||
|
|
||||||
Cette commande n'est nécessaire que la première fois (le contenu de la base
|
|
||||||
est effacé au début de chaque test, mais son schéma reste) et aussi si des
|
|
||||||
migrations (changements de schéma) ont eu lieu dans le code.
|
|
||||||
|
|
||||||
Certains tests ont besoin d'un département déjà créé, qui n'est pas créé par les
|
|
||||||
scripts de tests:
|
|
||||||
Lancer au préalable:
|
|
||||||
|
|
||||||
flask delete-dept TEST00 && flask create-dept TEST00
|
|
||||||
|
|
||||||
Puis dérouler les tests unitaires:
|
|
||||||
|
|
||||||
pytest tests/unit
|
|
||||||
|
|
||||||
Ou avec couverture (`pip install pytest-cov`)
|
|
||||||
|
|
||||||
pytest --cov=app --cov-report=term-missing --cov-branch tests/unit/*
|
|
||||||
|
|
||||||
|
|
||||||
#### Utilisation des tests unitaires pour initialiser la base de dev
|
|
||||||
On peut aussi utiliser les tests unitaires pour mettre la base
|
|
||||||
de données de développement dans un état connu, par exemple pour éviter de
|
|
||||||
recréer à la main étudiants et semestres quand on développe.
|
|
||||||
|
|
||||||
Il suffit de positionner une variable d'environnement indiquant la BD
|
|
||||||
utilisée par les tests:
|
|
||||||
|
|
||||||
export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV
|
|
||||||
|
|
||||||
(si elle n'existe pas, voir plus loin pour la créer) puis de les lancer
|
|
||||||
normalement, par exemple:
|
|
||||||
|
|
||||||
pytest tests/unit/test_sco_basic.py
|
|
||||||
|
|
||||||
Il est en général nécessaire d'affecter ensuite un mot de passe à (au moins)
|
|
||||||
un utilisateur:
|
|
||||||
|
|
||||||
flask user-password admin
|
|
||||||
|
|
||||||
**Attention:** les tests unitaires **effacent** complètement le contenu de la
|
|
||||||
base de données (tous les départements, et les utilisateurs) avant de commencer !
|
|
||||||
|
|
||||||
#### Modification du schéma de la base
|
|
||||||
|
|
||||||
On utilise SQLAlchemy avec Alembic et Flask-Migrate.
|
|
||||||
|
|
||||||
flask db migrate -m "message explicatif....."
|
|
||||||
flask db upgrade
|
|
||||||
|
|
||||||
Ne pas oublier de d'ajouter le script de migration à git (`git add migrations/...`).
|
|
||||||
|
|
||||||
**Mémo**: séquence re-création d'une base (vérifiez votre `.env`
|
|
||||||
ou variables d'environnement pour interroger la bonne base !).
|
|
||||||
|
|
||||||
dropdb SCODOC_DEV
|
|
||||||
tools/create_database.sh SCODOC_DEV # créé base SQL
|
|
||||||
flask db upgrade # créé les tables à partir des migrations
|
|
||||||
flask sco-db-init # ajoute au besoin les constantes (fait en migration 0)
|
|
||||||
|
|
||||||
# puis imports:
|
|
||||||
flask import-scodoc7-users
|
|
||||||
flask import-scodoc7-dept STID SCOSTID
|
|
||||||
|
|
||||||
Si la base utilisée pour les dev n'est plus en phase avec les scripts de
|
|
||||||
migration, utiliser les commandes `flask db history`et `flask db stamp`pour se
|
|
||||||
positionner à la bonne étape.
|
|
||||||
|
|
||||||
### Profiling
|
|
||||||
|
|
||||||
Sur une machine de DEV, lancer
|
|
||||||
|
|
||||||
flask profile --host 0.0.0.0 --length 32 --profile-dir /opt/scodoc-data
|
|
||||||
|
|
||||||
le fichier `.prof` sera alors écrit dans `/opt/scodoc-data` (on peut aussi utiliser `/tmp`).
|
|
||||||
|
|
||||||
Pour la visualisation, [snakeviz](https://jiffyclub.github.io/snakeviz/) est bien:
|
|
||||||
|
|
||||||
pip install snakeviz
|
|
||||||
|
|
||||||
puis
|
|
||||||
|
|
||||||
snakeviz -s --hostname 0.0.0.0 -p 5555 /opt/scodoc-data/GET.ScoDoc......prof
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Paquet Debian 11
|
|
||||||
|
|
||||||
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`. Le plus
|
|
||||||
important est `postinst`qui se charge de configurer le système (install ou
|
|
||||||
upgrade de scodoc9).
|
|
||||||
|
|
||||||
La préparation d'une release se fait à l'aide du script
|
|
||||||
`tools/build_release.sh`.
|
|
||||||
|
|
||||||
|
206
SuppressAccents.py
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Suppression des accents d'une chaine
|
||||||
|
|
||||||
|
Source: http://wikipython.flibuste.net/moin.py/JouerAvecUnicode#head-1213938516c633958921591439c33d202244e2f4
|
||||||
|
"""
|
||||||
|
|
||||||
|
_reptable = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _fill_reptable():
|
||||||
|
_corresp = [
|
||||||
|
(
|
||||||
|
u"A",
|
||||||
|
[0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x0100, 0x0102, 0x0104],
|
||||||
|
),
|
||||||
|
(u"AE", [0x00C6]),
|
||||||
|
(
|
||||||
|
u"a",
|
||||||
|
[0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x0101, 0x0103, 0x0105],
|
||||||
|
),
|
||||||
|
(u"ae", [0x00E6]),
|
||||||
|
(u"C", [0x00C7, 0x0106, 0x0108, 0x010A, 0x010C]),
|
||||||
|
(u"c", [0x00E7, 0x0107, 0x0109, 0x010B, 0x010D]),
|
||||||
|
(u"D", [0x00D0, 0x010E, 0x0110]),
|
||||||
|
(u"d", [0x00F0, 0x010F, 0x0111]),
|
||||||
|
(
|
||||||
|
u"E",
|
||||||
|
[0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x0112, 0x0114, 0x0116, 0x0118, 0x011A],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
u"e",
|
||||||
|
[
|
||||||
|
0x00E8,
|
||||||
|
0xE9,
|
||||||
|
0x00E9,
|
||||||
|
0x00EA,
|
||||||
|
0xEB,
|
||||||
|
0x00EB,
|
||||||
|
0x0113,
|
||||||
|
0x0115,
|
||||||
|
0x0117,
|
||||||
|
0x0119,
|
||||||
|
0x011B,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(u"G", [0x011C, 0x011E, 0x0120, 0x0122]),
|
||||||
|
(u"g", [0x011D, 0x011F, 0x0121, 0x0123]),
|
||||||
|
(u"H", [0x0124, 0x0126]),
|
||||||
|
(u"h", [0x0125, 0x0127]),
|
||||||
|
(
|
||||||
|
u"I",
|
||||||
|
[0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x0128, 0x012A, 0x012C, 0x012E, 0x0130],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
u"i",
|
||||||
|
[0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x0129, 0x012B, 0x012D, 0x012F, 0x0131],
|
||||||
|
),
|
||||||
|
(u"IJ", [0x0132]),
|
||||||
|
(u"ij", [0x0133]),
|
||||||
|
(u"J", [0x0134]),
|
||||||
|
(u"j", [0x0135]),
|
||||||
|
(u"K", [0x0136]),
|
||||||
|
(u"k", [0x0137, 0x0138]),
|
||||||
|
(u"L", [0x0139, 0x013B, 0x013D, 0x013F, 0x0141]),
|
||||||
|
(u"l", [0x013A, 0x013C, 0x013E, 0x0140, 0x0142]),
|
||||||
|
(u"N", [0x00D1, 0x0143, 0x0145, 0x0147, 0x014A]),
|
||||||
|
(u"n", [0x00F1, 0x0144, 0x0146, 0x0148, 0x0149, 0x014B]),
|
||||||
|
(
|
||||||
|
u"O",
|
||||||
|
[0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D8, 0x014C, 0x014E, 0x0150],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
u"o",
|
||||||
|
[0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F8, 0x014D, 0x014F, 0x0151],
|
||||||
|
),
|
||||||
|
(u"OE", [0x0152]),
|
||||||
|
(u"oe", [0x0153]),
|
||||||
|
(u"R", [0x0154, 0x0156, 0x0158]),
|
||||||
|
(u"r", [0x0155, 0x0157, 0x0159]),
|
||||||
|
(u"S", [0x015A, 0x015C, 0x015E, 0x0160]),
|
||||||
|
(u"s", [0x015B, 0x015D, 0x015F, 0x01610, 0x017F, 0x0218]),
|
||||||
|
(u"T", [0x0162, 0x0164, 0x0166]),
|
||||||
|
(u"t", [0x0163, 0x0165, 0x0167]),
|
||||||
|
(
|
||||||
|
u"U",
|
||||||
|
[
|
||||||
|
0x00D9,
|
||||||
|
0x00DA,
|
||||||
|
0x00DB,
|
||||||
|
0x00DC,
|
||||||
|
0x0168,
|
||||||
|
0x016A,
|
||||||
|
0x016C,
|
||||||
|
0x016E,
|
||||||
|
0x0170,
|
||||||
|
0x172,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
u"u",
|
||||||
|
[
|
||||||
|
0x00F9,
|
||||||
|
0x00FA,
|
||||||
|
0x00FB,
|
||||||
|
0x00FC,
|
||||||
|
0x0169,
|
||||||
|
0x016B,
|
||||||
|
0x016D,
|
||||||
|
0x016F,
|
||||||
|
0x0171,
|
||||||
|
0xB5,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(u"W", [0x0174]),
|
||||||
|
(u"w", [0x0175]),
|
||||||
|
(u"Y", [0x00DD, 0x0176, 0x0178]),
|
||||||
|
(u"y", [0x00FD, 0x00FF, 0x0177]),
|
||||||
|
(u"Z", [0x0179, 0x017B, 0x017D]),
|
||||||
|
(u"z", [0x017A, 0x017C, 0x017E]),
|
||||||
|
(
|
||||||
|
u"",
|
||||||
|
[
|
||||||
|
0x80,
|
||||||
|
0x81,
|
||||||
|
0x82,
|
||||||
|
0x83,
|
||||||
|
0x84,
|
||||||
|
0x85,
|
||||||
|
0x86,
|
||||||
|
0x87,
|
||||||
|
0x88,
|
||||||
|
0x89,
|
||||||
|
0x8A,
|
||||||
|
0x8B,
|
||||||
|
0x8C,
|
||||||
|
0x8D,
|
||||||
|
0x8E,
|
||||||
|
0x8F,
|
||||||
|
0x90,
|
||||||
|
0x91,
|
||||||
|
0x92,
|
||||||
|
0x93,
|
||||||
|
0x94,
|
||||||
|
0x95,
|
||||||
|
0x96,
|
||||||
|
0x97,
|
||||||
|
0x98,
|
||||||
|
0x99,
|
||||||
|
0x9A,
|
||||||
|
0x9B,
|
||||||
|
0x9C,
|
||||||
|
0x9D,
|
||||||
|
0x9E,
|
||||||
|
0x9F,
|
||||||
|
],
|
||||||
|
), # misc controls
|
||||||
|
(u" ", [0x00A0]), #  
|
||||||
|
(u"!", [0xA1]), # ¡
|
||||||
|
(u"c", [0xA2]), # cent
|
||||||
|
(u"L", [0xA3]), # pound
|
||||||
|
(u"o", [0xA4]), # currency symbol
|
||||||
|
(u"Y", [0xA5]), # yen
|
||||||
|
(u"|", [0xA6]), # Broken Bar ¦
|
||||||
|
(u"S", [0xA7]), # section
|
||||||
|
(u"", [0xA8]), # diaeresis ¨
|
||||||
|
(u"", [0xA9]), # copyright
|
||||||
|
(u'"', [0xAB, 0xBA]), # «, » <<, >>
|
||||||
|
(u" ", [0xAC]), # Math Not Sign
|
||||||
|
(u"", [0xAD]), # DashPunctuation
|
||||||
|
(u"(r)", [0xAE]), # registred
|
||||||
|
(u"-", [0xAF]), # macron
|
||||||
|
(u"", [0xB0]), # degre
|
||||||
|
(u"+-", [0xB1]), # +-
|
||||||
|
(u"2", [0x00B2, 0xB2]), # deux exposant
|
||||||
|
(u"3", [0xB3]), # 3 exposant
|
||||||
|
(u".", [0xB7]), # ·,
|
||||||
|
(u"1/4", [0xBC]), # 1/4
|
||||||
|
(u"1/2", [0xBD]), # 1/2
|
||||||
|
(u"3/4", [0xBE]), # 3/4
|
||||||
|
(u"e", [0x20AC]), # euro
|
||||||
|
(u"--", [0x2013]), # EN DASH
|
||||||
|
(u"'", [0x2018, 0x2019, 0x201A]), # LEFT, RIGHT SINGLE QUOTATION MARK
|
||||||
|
(u" ", [0x2020]), # dagger
|
||||||
|
]
|
||||||
|
global _reptable
|
||||||
|
for repchar, codes in _corresp:
|
||||||
|
for code in codes:
|
||||||
|
_reptable[code] = repchar
|
||||||
|
|
||||||
|
|
||||||
|
_fill_reptable()
|
||||||
|
|
||||||
|
|
||||||
|
def suppression_diacritics(s):
|
||||||
|
"""Suppression des accents et autres marques.
|
||||||
|
|
||||||
|
@param s: le texte à nettoyer.
|
||||||
|
@type s: str ou unicode
|
||||||
|
@return: le texte nettoyé de ses marques diacritiques.
|
||||||
|
@rtype: unicode
|
||||||
|
"""
|
||||||
|
if isinstance(s, str):
|
||||||
|
s = unicode(s, "utf8", "replace")
|
||||||
|
return s.translate(_reptable)
|
238
TODO
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
|
||||||
|
NOTES EN VRAC / Brouillon / Trucs obsoletes
|
||||||
|
|
||||||
|
|
||||||
|
#do_moduleimpl_list\(\{"([a-z_]*)"\s*:\s*(.*)\}\)
|
||||||
|
#do_moduleimpl_list( $1 = $2 )
|
||||||
|
|
||||||
|
#do_moduleimpl_list\([\s\n]*args[\s\n]*=[\s\n]*\{"([a-z_]*)"[\s\n]*:[\s\n]*(.*)[\s\n]*\}[\s\n]*\)
|
||||||
|
|
||||||
|
Upgrade JavaScript
|
||||||
|
- jquery-ui-1.12.1 introduit un problème d'affichage de la barre de menu.
|
||||||
|
Il faudrait la revoir entièrement pour upgrader.
|
||||||
|
On reste donc à jquery-ui-1.10.4.custom
|
||||||
|
Or cette version est incompatible avec jQuery 3 (messages d'erreur dans la console)
|
||||||
|
On reste donc avec jQuery 1.12.14
|
||||||
|
|
||||||
|
|
||||||
|
Suivi des requêtes utilisateurs:
|
||||||
|
table sql: id, ip, authuser, request
|
||||||
|
|
||||||
|
|
||||||
|
* Optim:
|
||||||
|
porcodeb4, avant memorisation des moy_ue:
|
||||||
|
S1 SEM14133 cold start: min 9s, max 12s, avg > 11s
|
||||||
|
inval (add note): 1.33s (pas de recalcul des autres)
|
||||||
|
inval (add abs) : min8s, max 12s (recalcule tout :-()
|
||||||
|
LP SEM14946 cold start: 0.7s - 0.86s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----------------- LISTE OBSOLETE (très ancienne, à trier) -----------------------
|
||||||
|
BUGS
|
||||||
|
----
|
||||||
|
|
||||||
|
- formsemestre_inscription_with_modules
|
||||||
|
si inscription 'un etud deja inscrit, IntegrityError
|
||||||
|
|
||||||
|
FEATURES REQUESTS
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
* Bulletins:
|
||||||
|
. logos IUT et Univ sur bull PDF
|
||||||
|
. nom departement: nom abbrégé (CJ) ou complet (Carrière Juridiques)
|
||||||
|
. bulletin: deplacer la barre indicateur (cf OLDGEA S2: gêne)
|
||||||
|
. bulletin: click nom titre -> ficheEtud
|
||||||
|
|
||||||
|
. formsemestre_pagebulletin_dialog: marges en mm: accepter "2,5" et "2.5"
|
||||||
|
et valider correctement le form !
|
||||||
|
|
||||||
|
* Jury
|
||||||
|
. recapcomplet: revenir avec qq lignes au dessus de l'étudiant en cours
|
||||||
|
|
||||||
|
|
||||||
|
* Divers
|
||||||
|
. formsemestre_editwithmodules: confirmer suppression modules
|
||||||
|
(et pour l'instant impossible si evaluations dans le module)
|
||||||
|
|
||||||
|
* Modules et UE optionnelles:
|
||||||
|
. UE capitalisées: donc dispense possible dans semestre redoublé.
|
||||||
|
traitable en n'inscrivant pas l'etudiant au modules
|
||||||
|
de cette UE: faire interface utilisateur
|
||||||
|
|
||||||
|
. page pour inscription d'un etudiant a un module
|
||||||
|
. page pour visualiser les modules auquel un etudiant est inscrit,
|
||||||
|
et le desinscrire si besoin.
|
||||||
|
|
||||||
|
. ficheEtud indiquer si inscrit au module sport
|
||||||
|
|
||||||
|
* Absences
|
||||||
|
. EtatAbsences : verifier dates (en JS)
|
||||||
|
. Listes absences pdf et listes groupes pdf + emargements (cf mail Nathalie)
|
||||||
|
. absences par demi-journées sur EtatAbsencesDate (? à vérifier)
|
||||||
|
. formChoixSemestreGroupe: utilisé par Absences/index_html
|
||||||
|
a améliorer
|
||||||
|
|
||||||
|
|
||||||
|
* Notes et évaluations:
|
||||||
|
. Exception "Not an OLE file": generer page erreur plus explicite
|
||||||
|
. Dates evaluation: utiliser JS pour calendrier
|
||||||
|
. Saisie des notes: si une note invalide, l'indiquer dans le listing (JS ?)
|
||||||
|
. et/ou: notes invalides: afficher les noms des etudiants concernes
|
||||||
|
dans le message d'erreur.
|
||||||
|
. upload excel: message erreur peu explicite:
|
||||||
|
* Feuille "Saisie notes", 17 lignes
|
||||||
|
* Erreur: la feuille contient 1 notes invalides
|
||||||
|
* Notes invalides pour les id: ['10500494']
|
||||||
|
(pas de notes modifiées)
|
||||||
|
Notes chargées. <<< CONTRADICTOIRE !!
|
||||||
|
|
||||||
|
. recap complet semestre:
|
||||||
|
Options:
|
||||||
|
- choix groupes
|
||||||
|
- critère de tri (moy ou alphab)
|
||||||
|
- nb de chiffres a afficher
|
||||||
|
|
||||||
|
+ definir des "catégories" d'évaluations (eg "théorie","pratique")
|
||||||
|
afin de n'afficher que des moyennes "de catégorie" dans
|
||||||
|
le bulletin.
|
||||||
|
|
||||||
|
. liste des absents à une eval et croisement avec BD absences
|
||||||
|
|
||||||
|
. notes_evaluation_listenotes
|
||||||
|
- afficher groupes, moyenne, #inscrits, #absents, #manquantes dans l'en-tete.
|
||||||
|
- lien vers modif notes (selon role)
|
||||||
|
|
||||||
|
. Export excel des notes d'evaluation: indiquer date, et autres infos en haut.
|
||||||
|
. Génération PDF listes notes
|
||||||
|
. Page recap notes moyennes par groupes (choisir type de groupe?)
|
||||||
|
|
||||||
|
. (GEA) edition tableau notes avec tous les evals d'un module
|
||||||
|
(comme notes_evaluation_listenotes mais avec tt les evals)
|
||||||
|
|
||||||
|
|
||||||
|
* Non prioritaire:
|
||||||
|
. optimiser scolar_news_summary
|
||||||
|
. recapitulatif des "nouvelles"
|
||||||
|
- dernieres notes
|
||||||
|
- changement de statuts (demissions,inscriptions)
|
||||||
|
- annotations
|
||||||
|
- entreprises
|
||||||
|
|
||||||
|
. notes_table: pouvoir changer decision sans invalider tout le cache
|
||||||
|
. navigation: utiliser Session pour montrer historique pages vues ?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
A faire:
|
||||||
|
- fiche etud: code dec jury sur ligne 1
|
||||||
|
si ancien, indiquer autorisation inscription sous le parcours
|
||||||
|
|
||||||
|
- saisie notes: undo
|
||||||
|
- saisie notes: validation
|
||||||
|
- ticket #18:
|
||||||
|
UE capitalisées: donc dispense possible dans semestre redoublé. Traitable en n'inscrivant pas l'etudiant aux modules de cette UE: faire interface utilisateur.
|
||||||
|
|
||||||
|
Prévoir d'entrer une UE capitalisée avec sa note, date d'obtention et un commentaire. Coupler avec la désincription aux modules (si l'étudiant a été inscrit avec ses condisciples).
|
||||||
|
|
||||||
|
|
||||||
|
- Ticket #4: Afin d'éviter les doublons, vérifier qu'il n'existe pas d'homonyme proche lors de la création manuelle d'un étudiant. (confirmé en ScoDoc 6, vérifier aussi les imports Excel)
|
||||||
|
|
||||||
|
- Ticket #74: Il est possible d'inscrire un étudiant sans prénom par un import excel !!!
|
||||||
|
|
||||||
|
- Ticket #64: saisir les absences pour la promo entiere (et pas par groupe). Des fois, je fais signer une feuille de presence en amphi a partir de la liste de tous les etudiants. Ensuite pour reporter les absents par groupe, c'est galere.
|
||||||
|
|
||||||
|
- Ticket #62: Lors des exports Excel, le format des cellules n'est pas reconnu comme numérique sous Windows (pas de problèmes avec Macintosh et Linux).
|
||||||
|
|
||||||
|
A confirmer et corriger.
|
||||||
|
|
||||||
|
- Ticket #75: On peut modifier une décision de jury (et les autorisations de passage associées), mais pas la supprimer purement et simplement.
|
||||||
|
Ajoute ce choix dans les "décisions manuelles".
|
||||||
|
|
||||||
|
- Ticket #37: Page recap notes moyennes par groupes
|
||||||
|
Construire une page avec les moyennes dans chaque UE ou module par groupe d'étudiants.
|
||||||
|
Et aussi pourquoi pas ventiler par type de bac, sexe, parcours (nombre de semestre de parcours) ?
|
||||||
|
redemandé par CJ: à faire avant mai 2008 !
|
||||||
|
|
||||||
|
- Ticket #75: Synchro Apogée: choisir les etudiants
|
||||||
|
Sur la page de syncho Apogée (formsemestre_synchro_etuds), on peut choisir (cocher) les étudiants Apogée à importer. mais on ne peut pas le faire s'ils sont déjà dans ScoDoc: il faudrait ajouter des checkboxes dans toutes les listes.
|
||||||
|
|
||||||
|
- Ticket #9: Format des valeurs de marges des bulletins.
|
||||||
|
formsemestre_pagebulletin_dialog: marges en mm: accepter "2,5" et "2.5" et valider correctement le form !
|
||||||
|
|
||||||
|
- Ticket #17: Suppression modules dans semestres
|
||||||
|
formsemestre_editwithmodules: confirmer suppression modules
|
||||||
|
|
||||||
|
- Ticket #29: changer le stoquage des photos, garder une version HD.
|
||||||
|
|
||||||
|
- bencher NotesTable sans calcul de moyennes. Etudier un cache des moyennes de modules.
|
||||||
|
- listes d'utilisateurs (modules): remplacer menus par champs texte + completions javascript
|
||||||
|
- documenter archives sur Wiki
|
||||||
|
- verifier paquet Debian pour font pdf (reportab: helvetica ... plante si font indisponible)
|
||||||
|
- chercher comment obtenir une page d'erreur correcte pour les pages POST
|
||||||
|
(eg: si le font n'existe pas, archive semestre echoue sans page d'erreur)
|
||||||
|
? je ne crois pas que le POST soit en cause. HTTP status=500
|
||||||
|
ne se produit pas avec Safari
|
||||||
|
- essayer avec IE / Win98
|
||||||
|
- faire apparaitre les diplômés sur le graphe des parcours
|
||||||
|
- démission: formulaire: vérifier que la date est bien dans le semestre
|
||||||
|
|
||||||
|
+ graphe parcours: aligner en colonnes selon les dates (de fin), placer les diplomes
|
||||||
|
dans la même colone que le semestre terminal.
|
||||||
|
|
||||||
|
- modif gestion utilisateurs (donner droits en fct du dept. d'appartenance, bug #57)
|
||||||
|
- modif form def. utilisateur (dept appartenance)
|
||||||
|
- utilisateurs: source externe
|
||||||
|
- archivage des semestres
|
||||||
|
|
||||||
|
|
||||||
|
o-------------------------------------o
|
||||||
|
|
||||||
|
* Nouvelle gestion utilisateurs:
|
||||||
|
objectif: dissocier l'authentification de la notion "d'enseignant"
|
||||||
|
On a une source externe "d'utilisateurs" (annuaire LDAP ou base SQL)
|
||||||
|
qui permet seulement de:
|
||||||
|
- authentifier un utilisateur (login, passwd)
|
||||||
|
- lister un utilisateur: login => firstname, lastname, email
|
||||||
|
- lister les utilisateurs
|
||||||
|
|
||||||
|
et une base interne ScoDoc "d'acteurs" (enseignants, administratifs).
|
||||||
|
Chaque acteur est défini par:
|
||||||
|
- actor_id, firstname, lastname
|
||||||
|
date_creation, date_expiration,
|
||||||
|
roles, departement,
|
||||||
|
email (+flag indiquant s'il faut utiliser ce mail ou celui de
|
||||||
|
l'utilisateur ?)
|
||||||
|
state (on, off) (pour desactiver avant expiration ?)
|
||||||
|
user_id (login) => lien avec base utilisateur
|
||||||
|
|
||||||
|
On offrira une source d'utilisateurs SQL (base partagée par tous les dept.
|
||||||
|
d'une instance ScoDoc), mais dans la plupart des cas les gens utiliseront
|
||||||
|
un annuaire LDAP.
|
||||||
|
|
||||||
|
La base d'acteurs remplace ScoUsers. Les objets ScoDoc (semestres,
|
||||||
|
modules etc) font référence à des acteurs (eg responsable_id est un actor_id).
|
||||||
|
|
||||||
|
Le lien entre les deux ?
|
||||||
|
Loger un utilisateur => authentification utilisateur + association d'un acteur
|
||||||
|
Cela doit se faire au niveau d'un UserFolder Zope, pour avoir les
|
||||||
|
bons rôles et le contrôle d'accès adéquat.
|
||||||
|
(Il faut donc coder notre propre UserFolder).
|
||||||
|
On ne peut associer qu'un acteur à l'état 'on' et non expiré.
|
||||||
|
|
||||||
|
Opérations ScoDoc:
|
||||||
|
- paramétrage: choisir et paramétrer source utilisateurs
|
||||||
|
- ajouter utilisateur: choisir un utilisateur dans la liste
|
||||||
|
et lui associer un nouvel acteur (choix des rôles, des dates)
|
||||||
|
+ éventuellement: synchro d'un ensemble d'utilisateurs, basé sur
|
||||||
|
une requête (eg LDAP) précise (quelle interface utilisateur proposer ?)
|
||||||
|
|
||||||
|
- régulièrement (cron) aviser quelqu'un (le chef) de l'expiration des acteurs.
|
||||||
|
- changer etat d'un acteur (on/off)
|
||||||
|
|
||||||
|
|
||||||
|
o-------------------------------------o
|
||||||
|
|
@ -6,15 +6,10 @@
|
|||||||
|
|
||||||
E. Viennet 2005 - 2008
|
E. Viennet 2005 - 2008
|
||||||
|
|
||||||
v 1.3 (python3)
|
v 1.2
|
||||||
"""
|
"""
|
||||||
import html
|
|
||||||
import re
|
|
||||||
|
|
||||||
# re validant dd/mm/yyyy
|
from types import BooleanType, StringType
|
||||||
DMY_REGEXP = re.compile(
|
|
||||||
r"^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def TrivialFormulator(
|
def TrivialFormulator(
|
||||||
@ -57,7 +52,7 @@ def TrivialFormulator(
|
|||||||
allow_null : if true, field can be left empty (default true)
|
allow_null : if true, field can be left empty (default true)
|
||||||
type : 'string', 'int', 'float' (default to string), 'list' (only for hidden)
|
type : 'string', 'int', 'float' (default to string), 'list' (only for hidden)
|
||||||
readonly : default False. if True, no form element, display current value.
|
readonly : default False. if True, no form element, display current value.
|
||||||
convert_numbers: convert int and float values (from string)
|
convert_numbers: covert int and float values (from string)
|
||||||
allowed_values : list of possible values (default: any value)
|
allowed_values : list of possible values (default: any value)
|
||||||
validator : function validating the field (called with (value,field)).
|
validator : function validating the field (called with (value,field)).
|
||||||
min_value : minimum value (for floats and ints)
|
min_value : minimum value (for floats and ints)
|
||||||
@ -72,8 +67,8 @@ def TrivialFormulator(
|
|||||||
HTML elements:
|
HTML elements:
|
||||||
input_type : 'text', 'textarea', 'password',
|
input_type : 'text', 'textarea', 'password',
|
||||||
'radio', 'menu', 'checkbox',
|
'radio', 'menu', 'checkbox',
|
||||||
'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
|
'hidden', 'separator', 'file', 'date', 'boolcheckbox',
|
||||||
'boolcheckbox', 'text_suggest'
|
'text_suggest'
|
||||||
(default text)
|
(default text)
|
||||||
size : text field width
|
size : text field width
|
||||||
rows, cols: textarea geometry
|
rows, cols: textarea geometry
|
||||||
@ -118,7 +113,7 @@ def TrivialFormulator(
|
|||||||
return res, form, t.result
|
return res, form, t.result
|
||||||
|
|
||||||
|
|
||||||
class TF(object):
|
class TF:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
form_url,
|
form_url,
|
||||||
@ -141,7 +136,7 @@ class TF(object):
|
|||||||
is_submitted=False,
|
is_submitted=False,
|
||||||
):
|
):
|
||||||
self.form_url = form_url
|
self.form_url = form_url
|
||||||
self.values = values.copy()
|
self.values = values
|
||||||
self.formdescription = list(formdescription)
|
self.formdescription = list(formdescription)
|
||||||
self.initvalues = initvalues
|
self.initvalues = initvalues
|
||||||
self.method = method
|
self.method = method
|
||||||
@ -170,7 +165,7 @@ class TF(object):
|
|||||||
"true if form has been submitted"
|
"true if form has been submitted"
|
||||||
if self.is_submitted:
|
if self.is_submitted:
|
||||||
return True
|
return True
|
||||||
return self.values.get("%s_submitted" % self.formid, False)
|
return self.values.get("%s-submitted" % self.formid, False)
|
||||||
|
|
||||||
def canceled(self):
|
def canceled(self):
|
||||||
"true if form has been canceled"
|
"true if form has been canceled"
|
||||||
@ -201,29 +196,25 @@ class TF(object):
|
|||||||
for (field, descr) in self.formdescription:
|
for (field, descr) in self.formdescription:
|
||||||
# special case for boolcheckbox
|
# special case for boolcheckbox
|
||||||
if descr.get("input_type", None) == "boolcheckbox" and self.submitted():
|
if descr.get("input_type", None) == "boolcheckbox" and self.submitted():
|
||||||
if field not in self.values:
|
if not self.values.has_key(field):
|
||||||
self.values[field] = 0
|
self.values[field] = 0
|
||||||
else:
|
else:
|
||||||
self.values[field] = 1
|
self.values[field] = 1
|
||||||
if field not in self.values:
|
if not self.values.has_key(field):
|
||||||
if "default" in descr: # first: default in form description
|
if descr.has_key("default"): # first: default in form description
|
||||||
self.values[field] = descr["default"]
|
self.values[field] = descr["default"]
|
||||||
else: # then: use initvalues dict
|
else: # then: use initvalues dict
|
||||||
self.values[field] = self.initvalues.get(field, "")
|
self.values[field] = self.initvalues.get(field, "")
|
||||||
if self.values[field] == None:
|
if self.values[field] == None:
|
||||||
self.values[field] = ""
|
self.values[field] = ""
|
||||||
|
|
||||||
# convert numbers, except ids
|
# convert numbers
|
||||||
if field.endswith("id") and self.values[field]:
|
if type(self.values[field]) == type(1) or type(self.values[field]) == type(
|
||||||
# enforce integer ids:
|
1.0
|
||||||
try:
|
):
|
||||||
self.values[field] = int(self.values[field])
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
elif isinstance(self.values[field], (int, float)):
|
|
||||||
self.values[field] = str(self.values[field])
|
self.values[field] = str(self.values[field])
|
||||||
#
|
#
|
||||||
if "tf-checked" not in self.values:
|
if not self.values.has_key("tf-checked"):
|
||||||
if self.submitted():
|
if self.submitted():
|
||||||
# si rien n'est coché, tf-checked n'existe plus dans la reponse
|
# si rien n'est coché, tf-checked n'existe plus dans la reponse
|
||||||
self.values["tf-checked"] = []
|
self.values["tf-checked"] = []
|
||||||
@ -249,8 +240,6 @@ class TF(object):
|
|||||||
"Le champ '%s' doit être renseigné" % descr.get("title", field)
|
"Le champ '%s' doit être renseigné" % descr.get("title", field)
|
||||||
)
|
)
|
||||||
ok = 0
|
ok = 0
|
||||||
elif val == "" or val == None:
|
|
||||||
continue # allowed empty field, skip
|
|
||||||
# type
|
# type
|
||||||
typ = descr.get("type", "string")
|
typ = descr.get("type", "string")
|
||||||
if val != "" and val != None:
|
if val != "" and val != None:
|
||||||
@ -275,14 +264,14 @@ class TF(object):
|
|||||||
)
|
)
|
||||||
ok = 0
|
ok = 0
|
||||||
if typ[:3] == "int" or typ == "float" or typ == "real":
|
if typ[:3] == "int" or typ == "float" or typ == "real":
|
||||||
if "min_value" in descr and val < descr["min_value"]:
|
if descr.has_key("min_value") and val < descr["min_value"]:
|
||||||
msg.append(
|
msg.append(
|
||||||
"La valeur (%d) du champ '%s' est trop petite (min=%s)"
|
"La valeur (%d) du champ '%s' est trop petite (min=%s)"
|
||||||
% (val, field, descr["min_value"])
|
% (val, field, descr["min_value"])
|
||||||
)
|
)
|
||||||
ok = 0
|
ok = 0
|
||||||
|
|
||||||
if "max_value" in descr and val > descr["max_value"]:
|
if descr.has_key("max_value") and val > descr["max_value"]:
|
||||||
msg.append(
|
msg.append(
|
||||||
"La valeur (%s) du champ '%s' est trop grande (max=%s)"
|
"La valeur (%s) du champ '%s' est trop grande (max=%s)"
|
||||||
% (val, field, descr["max_value"])
|
% (val, field, descr["max_value"])
|
||||||
@ -290,7 +279,7 @@ class TF(object):
|
|||||||
ok = 0
|
ok = 0
|
||||||
|
|
||||||
# allowed values
|
# allowed values
|
||||||
if "allowed_values" in descr:
|
if descr.has_key("allowed_values"):
|
||||||
if descr.get("input_type", None) == "checkbox":
|
if descr.get("input_type", None) == "checkbox":
|
||||||
# for checkboxes, val is a list
|
# for checkboxes, val is a list
|
||||||
for v in val:
|
for v in val:
|
||||||
@ -304,20 +293,16 @@ class TF(object):
|
|||||||
elif not val in descr["allowed_values"]:
|
elif not val in descr["allowed_values"]:
|
||||||
msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field))
|
msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field))
|
||||||
ok = 0
|
ok = 0
|
||||||
if "validator" in descr:
|
if descr.has_key("validator"):
|
||||||
if not descr["validator"](val, field):
|
if not descr["validator"](val, field):
|
||||||
msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field))
|
msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field))
|
||||||
ok = 0
|
ok = 0
|
||||||
elif descr.get("input_type") == "datedmy":
|
|
||||||
if not DMY_REGEXP.match(val):
|
|
||||||
msg.append("valeur invalide (%s) pour la date '%s'" % (val, field))
|
|
||||||
ok = 0
|
|
||||||
# boolean checkbox
|
# boolean checkbox
|
||||||
if descr.get("input_type", None) == "boolcheckbox":
|
if descr.get("input_type", None) == "boolcheckbox":
|
||||||
if int(val):
|
if int(val):
|
||||||
self.values[field] = True
|
self.values[field] = 1
|
||||||
else:
|
else:
|
||||||
self.values[field] = False
|
self.values[field] = 0
|
||||||
# open('/tmp/toto','a').write('checkvalues: val=%s (%s) values[%s] = %s\n' % (val, type(val), field, self.values[field]))
|
# open('/tmp/toto','a').write('checkvalues: val=%s (%s) values[%s] = %s\n' % (val, type(val), field, self.values[field]))
|
||||||
if descr.get("convert_numbers", False):
|
if descr.get("convert_numbers", False):
|
||||||
if typ[:3] == "int":
|
if typ[:3] == "int":
|
||||||
@ -346,7 +331,7 @@ class TF(object):
|
|||||||
buttons_markup = ""
|
buttons_markup = ""
|
||||||
if self.submitbutton:
|
if self.submitbutton:
|
||||||
buttons_markup += (
|
buttons_markup += (
|
||||||
'<input type="submit" name="%s_submit" id="%s_submit" value="%s" %s>'
|
'<input type="submit" name="%s_submit" id="%s_submit" value="%s" %s/>'
|
||||||
% (
|
% (
|
||||||
self.formid,
|
self.formid,
|
||||||
self.formid,
|
self.formid,
|
||||||
@ -356,7 +341,7 @@ class TF(object):
|
|||||||
)
|
)
|
||||||
if self.cancelbutton:
|
if self.cancelbutton:
|
||||||
buttons_markup += (
|
buttons_markup += (
|
||||||
' <input type="submit" name="%s_cancel" id="%s_cancel" value="%s">'
|
' <input type="submit" name="%s_cancel" id="%s_cancel" value="%s"/>'
|
||||||
% (self.formid, self.formid, self.cancelbutton)
|
% (self.formid, self.formid, self.cancelbutton)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -376,7 +361,7 @@ class TF(object):
|
|||||||
'<form action="%s" method="%s" id="%s" enctype="%s" name="%s" %s>'
|
'<form action="%s" method="%s" id="%s" enctype="%s" name="%s" %s>'
|
||||||
% (self.form_url, self.method, self.formid, enctype, name, klass)
|
% (self.form_url, self.method, self.formid, enctype, name, klass)
|
||||||
)
|
)
|
||||||
R.append('<input type="hidden" name="%s_submitted" value="1">' % self.formid)
|
R.append('<input type="hidden" name="%s-submitted" value="1"/>' % self.formid)
|
||||||
if self.top_buttons:
|
if self.top_buttons:
|
||||||
R.append(buttons_markup + "<p></p>")
|
R.append(buttons_markup + "<p></p>")
|
||||||
R.append('<table class="tf">')
|
R.append('<table class="tf">')
|
||||||
@ -418,7 +403,7 @@ class TF(object):
|
|||||||
else:
|
else:
|
||||||
checked = ""
|
checked = ""
|
||||||
lab.append(
|
lab.append(
|
||||||
'<input type="checkbox" name="%s:list" value="%s" onclick="tf_enable_elem(this)" %s>'
|
'<input type="checkbox" name="%s:list" value="%s" onclick="tf_enable_elem(this)" %s/>'
|
||||||
% ("tf-checked", field, checked)
|
% ("tf-checked", field, checked)
|
||||||
)
|
)
|
||||||
if title_bubble:
|
if title_bubble:
|
||||||
@ -451,13 +436,13 @@ class TF(object):
|
|||||||
add_no_enter_js = True
|
add_no_enter_js = True
|
||||||
# lem.append('onchange="document.%s.%s.focus()"'%(name,nextitemname))
|
# lem.append('onchange="document.%s.%s.focus()"'%(name,nextitemname))
|
||||||
# lem.append('onblur="document.%s.%s.focus()"'%(name,nextitemname))
|
# lem.append('onblur="document.%s.%s.focus()"'%(name,nextitemname))
|
||||||
lem.append(('value="%(' + field + ')s" >') % values)
|
lem.append(('value="%(' + field + ')s" />') % values)
|
||||||
elif input_type == "password":
|
elif input_type == "password":
|
||||||
lem.append(
|
lem.append(
|
||||||
'<input type="password" name="%s" id="%s" size="%d" %s'
|
'<input type="password" name="%s" id="%s" size="%d" %s'
|
||||||
% (field, wid, size, attribs)
|
% (field, wid, size, attribs)
|
||||||
)
|
)
|
||||||
lem.append(('value="%(' + field + ')s" >') % values)
|
lem.append(('value="%(' + field + ')s" />') % values)
|
||||||
elif input_type == "radio":
|
elif input_type == "radio":
|
||||||
labels = descr.get("labels", descr["allowed_values"])
|
labels = descr.get("labels", descr["allowed_values"])
|
||||||
for i in range(len(labels)):
|
for i in range(len(labels)):
|
||||||
@ -500,27 +485,19 @@ class TF(object):
|
|||||||
lem.append("<table>")
|
lem.append("<table>")
|
||||||
for i in range(len(labels)):
|
for i in range(len(labels)):
|
||||||
if input_type == "checkbox":
|
if input_type == "checkbox":
|
||||||
# from app.scodoc.sco_utils import log # debug only
|
# from notes_log import log # debug only
|
||||||
# log('checkbox: values[%s] = "%s"' % (field,repr(values[field]) ))
|
# log('checkbox: values[%s] = "%s"' % (field,repr(values[field]) ))
|
||||||
# log("descr['allowed_values'][%s] = '%s'" % (i, repr(descr['allowed_values'][i])))
|
# log("descr['allowed_values'][%s] = '%s'" % (i, repr(descr['allowed_values'][i])))
|
||||||
if (
|
if descr["allowed_values"][i] in values[field]:
|
||||||
values[field]
|
|
||||||
and descr["allowed_values"][i] in values[field]
|
|
||||||
):
|
|
||||||
checked = 'checked="checked"'
|
checked = 'checked="checked"'
|
||||||
else:
|
else:
|
||||||
checked = ""
|
checked = ""
|
||||||
else: # boolcheckbox
|
else: # boolcheckbox
|
||||||
# open('/tmp/toto','a').write('GenForm: values[%s] = %s (%s)\n' % (field, values[field], type(values[field])))
|
# open('/tmp/toto','a').write('GenForm: values[%s] = %s (%s)\n' % (field, values[field], type(values[field])))
|
||||||
if values[field] == "True":
|
|
||||||
v = True
|
|
||||||
elif values[field] == "False":
|
|
||||||
v = False
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
v = int(values[field])
|
v = int(values[field])
|
||||||
except:
|
except:
|
||||||
v = False
|
v = 0
|
||||||
if v:
|
if v:
|
||||||
checked = 'checked="checked"'
|
checked = 'checked="checked"'
|
||||||
else:
|
else:
|
||||||
@ -561,26 +538,24 @@ class TF(object):
|
|||||||
if descr.get("type", "") == "list":
|
if descr.get("type", "") == "list":
|
||||||
for v in values[field]:
|
for v in values[field]:
|
||||||
lem.append(
|
lem.append(
|
||||||
'<input type="hidden" name="%s:list" value="%s" %s >'
|
'<input type="hidden" name="%s:list" value="%s" %s />'
|
||||||
% (field, v, attribs)
|
% (field, v, attribs)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
lem.append(
|
lem.append(
|
||||||
'<input type="hidden" name="%s" id="%s" value="%s" %s >'
|
'<input type="hidden" name="%s" id="%s" value="%s" %s />'
|
||||||
% (field, wid, values[field], attribs)
|
% (field, wid, values[field], attribs)
|
||||||
)
|
)
|
||||||
elif input_type == "separator":
|
elif input_type == "separator":
|
||||||
pass
|
pass
|
||||||
elif input_type == "file":
|
elif input_type == "file":
|
||||||
lem.append(
|
lem.append(
|
||||||
'<input type="file" name="%s" size="%s" value="%s" %s>'
|
'<input type="file" name="%s" size="%s" value="%s" %s/>'
|
||||||
% (field, size, values[field], attribs)
|
% (field, size, values[field], attribs)
|
||||||
)
|
)
|
||||||
elif (
|
elif input_type == "date": # JavaScript widget for date input
|
||||||
input_type == "date" or input_type == "datedmy"
|
|
||||||
): # JavaScript widget for date input
|
|
||||||
lem.append(
|
lem.append(
|
||||||
'<input type="text" name="%s" size="10" value="%s" class="datepicker">'
|
'<input type="text" name="%s" size="10" value="%s" class="datepicker"/>'
|
||||||
% (field, values[field])
|
% (field, values[field])
|
||||||
)
|
)
|
||||||
elif input_type == "text_suggest":
|
elif input_type == "text_suggest":
|
||||||
@ -590,9 +565,16 @@ class TF(object):
|
|||||||
)
|
)
|
||||||
lem.append(('value="%(' + field + ')s" />') % values)
|
lem.append(('value="%(' + field + ')s" />') % values)
|
||||||
suggest_js.append(
|
suggest_js.append(
|
||||||
f"""var {field}_opts = {dict2js(descr.get("text_suggest_options", {}))};
|
"""var %s_opts = %s;
|
||||||
var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
var %s_as = new bsn.AutoSuggest('%s', %s_opts);
|
||||||
"""
|
"""
|
||||||
|
% (
|
||||||
|
field,
|
||||||
|
dict2js(descr.get("text_suggest_options", {})),
|
||||||
|
field,
|
||||||
|
field,
|
||||||
|
field,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError("unkown input_type for form (%s)!" % input_type)
|
raise ValueError("unkown input_type for form (%s)!" % input_type)
|
||||||
@ -719,27 +701,17 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||||||
labels = descr.get(
|
labels = descr.get(
|
||||||
"labels", descr.get("allowed_values", ["oui", "non"])
|
"labels", descr.get("allowed_values", ["oui", "non"])
|
||||||
)
|
)
|
||||||
_val = self.values[field]
|
# XXX open('/tmp/log', 'w').write('%s labels=%s, val=%s\ndescr=%s\n'%(field, labels, self.values[field], descr))
|
||||||
if isinstance(_val, bool):
|
R.append(labels[int(self.values[field])])
|
||||||
bool_val = 1 if _val else 0
|
if int(self.values[field]):
|
||||||
elif _val == "False":
|
R.append('<input type="hidden" name="%s" value="1"/>' % field)
|
||||||
bool_val = 0
|
|
||||||
elif _val:
|
|
||||||
bool_val = 1
|
|
||||||
else:
|
|
||||||
bool_val = 0
|
|
||||||
R.append(labels[bool_val])
|
|
||||||
if bool_val:
|
|
||||||
R.append('<input type="hidden" name="%s" value="1">' % field)
|
|
||||||
else:
|
else:
|
||||||
labels = descr.get("labels", descr["allowed_values"])
|
labels = descr.get("labels", descr["allowed_values"])
|
||||||
for i in range(len(labels)):
|
for i in range(len(labels)):
|
||||||
if str(descr["allowed_values"][i]) == str(self.values[field]):
|
if str(descr["allowed_values"][i]) == str(self.values[field]):
|
||||||
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
|
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
|
||||||
elif input_type == "textarea":
|
elif input_type == "textarea":
|
||||||
R.append(
|
R.append('<div class="tf-ro-textarea">%s</div>' % self.values[field])
|
||||||
'<div class="tf-ro-textarea">%s</div>' % html.escape(self.values[field])
|
|
||||||
)
|
|
||||||
elif input_type == "separator" or input_type == "hidden":
|
elif input_type == "separator" or input_type == "hidden":
|
||||||
pass
|
pass
|
||||||
elif input_type == "file":
|
elif input_type == "file":
|
||||||
@ -769,12 +741,12 @@ def dict2js(d):
|
|||||||
r = []
|
r = []
|
||||||
for k in d:
|
for k in d:
|
||||||
v = d[k]
|
v = d[k]
|
||||||
if isinstance(v, bool):
|
if type(v) == BooleanType:
|
||||||
if v:
|
if v:
|
||||||
v = "true"
|
v = "true"
|
||||||
else:
|
else:
|
||||||
v = "false"
|
v = "false"
|
||||||
elif isinstance(v, str): # ne marchera pas en python2
|
elif type(v) == StringType:
|
||||||
v = '"' + v + '"'
|
v = '"' + v + '"'
|
||||||
|
|
||||||
r.append("%s: %s" % (k, v))
|
r.append("%s: %s" % (k, v))
|
||||||
@ -785,9 +757,9 @@ def tf_error_message(msg):
|
|||||||
"""html for form error message"""
|
"""html for form error message"""
|
||||||
if not msg:
|
if not msg:
|
||||||
return ""
|
return ""
|
||||||
if isinstance(msg, str):
|
if type(msg) == StringType:
|
||||||
msg = [msg]
|
msg = [msg]
|
||||||
return (
|
return (
|
||||||
'<ul class="tf-msg"><li class="tf-msg error-message">%s</li></ul>'
|
'<ul class="tf-msg"><li class="tf-msg">%s</li></ul>'
|
||||||
% '</li><li class="tf-msg tf-msg error-message">'.join(msg)
|
% '</li><li class="tf-msg">'.join(msg)
|
||||||
)
|
)
|
@ -1,15 +1,13 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.1.32"
|
SCOVERSION = "7.25m"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
SCONEWS = """
|
SCONEWS = """
|
||||||
<h4>Année 2021</h4>
|
<h4>Année 2021</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>ScoDoc 9.1: gestion des formations par compétences, type BUT.</li>
|
|
||||||
<li>ScoDoc 9.0: nouvelle architecture logicielle (Flask/Python3/Debian 11)</li>
|
|
||||||
<li>Version mobile (en test)</li>
|
<li>Version mobile (en test)</li>
|
||||||
<li>Évaluations de type "deuxième session"</li>
|
<li>Évaluations de type "deuxième session"</li>
|
||||||
<li>Gestion du genre neutre (pas d'affichage de la civilité)</li>
|
<li>Gestion du genre neutre (pas d'affichage de la civilité)</li>
|
||||||
@ -19,7 +17,7 @@ SCONEWS = """
|
|||||||
<h4>Année 2020</h4>
|
<h4>Année 2020</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Corrections d'erreurs, améliorations saisie absences et affichage bulletins</li>
|
<li>Corrections d'erreurs, améliorations saisie absences et affichage bulletins</li>
|
||||||
<li>Nouveau site <a href="https://scodoc.org" target="_blank" rel="noopener noreferrer">scodoc.org</a> pour la documentation</li>
|
<li>Nouveau site <a href="https://scodoc.org">scodoc.org</a> pour la documentation</li>
|
||||||
<li>Enregistrement de semestres extérieurs</li>
|
<li>Enregistrement de semestres extérieurs</li>
|
||||||
<li>Améliorations PV de Jury</li>
|
<li>Améliorations PV de Jury</li>
|
||||||
<li>Contributions J.-M. Place: aide au diagnostic problèmes export Apogée
|
<li>Contributions J.-M. Place: aide au diagnostic problèmes export Apogée
|
||||||
@ -129,6 +127,7 @@ SCONEWS = """
|
|||||||
|
|
||||||
<h4>Janvier 2010</h4>
|
<h4>Janvier 2010</h4>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Suivez l'actualité du développement sur Twitter: <a href="https://twitter.com/ScoDoc">@ScoDoc</a></li>
|
||||||
<li>Nouveau menu "Groupes" pour faciliter la prise en main</li>
|
<li>Nouveau menu "Groupes" pour faciliter la prise en main</li>
|
||||||
<li>Possibilité de définir des règles ad hoc de calcul des moyennes de modules (formules)</li>
|
<li>Possibilité de définir des règles ad hoc de calcul des moyennes de modules (formules)</li>
|
||||||
<li>Possibilité d'inclure des images (logos) dans les bulletins PDF</li>
|
<li>Possibilité d'inclure des images (logos) dans les bulletins PDF</li>
|
1980
ZAbsences.py
Normal file
1301
ZEntreprises.py
Normal file
997
ZScoDoc.py
Normal file
@ -0,0 +1,997 @@
|
|||||||
|
# -*- 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
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Site ScoDoc pour plusieurs departements:
|
||||||
|
gestion de l'installation et des creation de départements.
|
||||||
|
|
||||||
|
Chaque departement est géré par un ZScolar sous ZScoDoc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
import string
|
||||||
|
import glob
|
||||||
|
import re
|
||||||
|
import inspect
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
import cgi
|
||||||
|
import xml
|
||||||
|
|
||||||
|
from cStringIO import StringIO
|
||||||
|
from zipfile import ZipFile
|
||||||
|
import os.path
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from email.MIMEMultipart import ( # pylint: disable=no-name-in-module,import-error
|
||||||
|
MIMEMultipart,
|
||||||
|
)
|
||||||
|
from email.MIMEText import MIMEText # pylint: disable=no-name-in-module,import-error
|
||||||
|
from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import-error
|
||||||
|
from email.Header import Header # pylint: disable=no-name-in-module,import-error
|
||||||
|
from email import Encoders # pylint: disable=no-name-in-module,import-error
|
||||||
|
|
||||||
|
from sco_zope import * # pylint: disable=unused-wildcard-import
|
||||||
|
|
||||||
|
try:
|
||||||
|
import Products.ZPsycopgDA.DA as ZopeDA
|
||||||
|
except:
|
||||||
|
import ZPsycopgDA.DA as ZopeDA # interp.py
|
||||||
|
|
||||||
|
import sco_utils as scu
|
||||||
|
import VERSION
|
||||||
|
from notes_log import log
|
||||||
|
import sco_find_etud
|
||||||
|
import sco_users
|
||||||
|
from sco_permissions import (
|
||||||
|
ScoView,
|
||||||
|
ScoEnsView,
|
||||||
|
ScoImplement,
|
||||||
|
ScoChangeFormation,
|
||||||
|
ScoObservateur,
|
||||||
|
ScoEtudInscrit,
|
||||||
|
ScoEtudChangeGroups,
|
||||||
|
ScoEtudChangeAdr,
|
||||||
|
ScoEtudSupprAnnotations,
|
||||||
|
ScoEditAllEvals,
|
||||||
|
ScoEditAllNotes,
|
||||||
|
ScoEditFormationTags,
|
||||||
|
ScoEditApo,
|
||||||
|
ScoSuperAdmin,
|
||||||
|
)
|
||||||
|
from sco_exceptions import ScoValueError, ScoLockedFormError, ScoGenError, AccessDenied
|
||||||
|
|
||||||
|
|
||||||
|
class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit):
|
||||||
|
|
||||||
|
"ZScoDoc object"
|
||||||
|
|
||||||
|
meta_type = "ZScoDoc"
|
||||||
|
security = ClassSecurityInfo()
|
||||||
|
file_path = Globals.package_home(globals())
|
||||||
|
|
||||||
|
# This is the list of the methods associated to 'tabs' in the ZMI
|
||||||
|
# Be aware that The first in the list is the one shown by default, so if
|
||||||
|
# the 'View' tab is the first, you will never see your tabs by cliquing
|
||||||
|
# on the object.
|
||||||
|
manage_options = (
|
||||||
|
({"label": "Contents", "action": "manage_main"},)
|
||||||
|
+ PropertyManager.manage_options # add the 'Properties' tab
|
||||||
|
+ ({"label": "View", "action": "index_html"},)
|
||||||
|
+ Item.manage_options # add the 'Undo' & 'Owner' tab
|
||||||
|
+ RoleManager.manage_options # add the 'Security' tab
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, id, title):
|
||||||
|
"Initialise a new instance of ZScoDoc"
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
self.manage_addProperty("admin_password_initialized", "0", "string")
|
||||||
|
|
||||||
|
security.declareProtected(ScoView, "ScoDocURL")
|
||||||
|
|
||||||
|
def ScoDocURL(self):
|
||||||
|
"base URL for this instance (top level for ScoDoc site)"
|
||||||
|
return self.absolute_url()
|
||||||
|
|
||||||
|
def _check_admin_perm(self, REQUEST):
|
||||||
|
"""Check if user has permission to add/delete departements"""
|
||||||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||||||
|
if authuser.has_role("manager") or authuser.has_permission(ScoSuperAdmin, self):
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return """<h2>Vous n'avez pas le droit d'accéder à cette page</h2>"""
|
||||||
|
|
||||||
|
def _check_users_folder(self, REQUEST=None):
|
||||||
|
"""Vérifie UserFolder et le crée s'il le faut"""
|
||||||
|
try:
|
||||||
|
_ = self.UsersDB
|
||||||
|
return "<!-- uf ok -->"
|
||||||
|
except:
|
||||||
|
e = self._check_admin_perm(REQUEST)
|
||||||
|
if not e: # admin permissions:
|
||||||
|
self.create_users_cnx(REQUEST)
|
||||||
|
self.create_users_folder(REQUEST)
|
||||||
|
return '<div class="head_message">Création du connecteur utilisateurs réussie</div>'
|
||||||
|
else:
|
||||||
|
return """<div class="head_message">Installation non terminée: connectez vous avec les droits d'administrateur</div>"""
|
||||||
|
|
||||||
|
security.declareProtected("View", "create_users_folder")
|
||||||
|
|
||||||
|
def create_users_folder(self, REQUEST=None):
|
||||||
|
"""Create Zope user folder"""
|
||||||
|
e = self._check_admin_perm(REQUEST)
|
||||||
|
if e:
|
||||||
|
return e
|
||||||
|
|
||||||
|
if REQUEST is None:
|
||||||
|
REQUEST = {}
|
||||||
|
|
||||||
|
REQUEST.form["pgauth_connection"] = "UsersDB"
|
||||||
|
REQUEST.form["pgauth_table"] = "sco_users"
|
||||||
|
REQUEST.form["pgauth_usernameColumn"] = "user_name"
|
||||||
|
REQUEST.form["pgauth_passwordColumn"] = "passwd"
|
||||||
|
REQUEST.form["pgauth_rolesColumn"] = "roles"
|
||||||
|
|
||||||
|
add_method = self.manage_addProduct["OFSP"].manage_addexUserFolder
|
||||||
|
log("create_users_folder: in %s" % self.id)
|
||||||
|
return add_method(
|
||||||
|
authId="pgAuthSource",
|
||||||
|
propId="nullPropSource",
|
||||||
|
memberId="nullMemberSource",
|
||||||
|
groupId="nullGroupSource",
|
||||||
|
cryptoId="MD51",
|
||||||
|
# doAuth='1', doProp='1', doMember='1', doGroup='1', allDone='1',
|
||||||
|
cookie_mode=2,
|
||||||
|
session_length=500,
|
||||||
|
not_session_length=0,
|
||||||
|
REQUEST=REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _fix_users_folder(self):
|
||||||
|
"""removes docLogin and docLogout dtml methods from exUserFolder, so that we use ours.
|
||||||
|
(called each time be index_html, to fix old ScoDoc installations.)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.acl_users.manage_delObjects(ids=["docLogin", "docLogout"])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# add missing getAuthFailedMessage (bug in exUserFolder ?)
|
||||||
|
try:
|
||||||
|
_ = self.getAuthFailedMessage
|
||||||
|
except:
|
||||||
|
log("adding getAuthFailedMessage to Zope install")
|
||||||
|
parent = self.aq_parent
|
||||||
|
from OFS.DTMLMethod import addDTMLMethod # pylint: disable=import-error
|
||||||
|
|
||||||
|
addDTMLMethod(parent, "getAuthFailedMessage", file="Identification")
|
||||||
|
|
||||||
|
security.declareProtected("View", "create_users_cnx")
|
||||||
|
|
||||||
|
def create_users_cnx(self, REQUEST=None):
|
||||||
|
"""Create Zope connector to UsersDB
|
||||||
|
|
||||||
|
Note: la connexion est fixée (SCOUSERS) (base crée par l'installeur) !
|
||||||
|
Les utilisateurs avancés pourront la changer ensuite.
|
||||||
|
"""
|
||||||
|
# ce connecteur zope - db est encore pour l'instant utilisé par exUserFolder.pgAuthSource
|
||||||
|
# (en lecture seule en principe)
|
||||||
|
oid = "UsersDB"
|
||||||
|
log("create_users_cnx: in %s" % self.id)
|
||||||
|
da = ZopeDA.Connection(
|
||||||
|
oid,
|
||||||
|
"Cnx bd utilisateurs",
|
||||||
|
scu.SCO_DEFAULT_SQL_USERS_CNX,
|
||||||
|
False,
|
||||||
|
check=1,
|
||||||
|
tilevel=2,
|
||||||
|
encoding="LATIN1",
|
||||||
|
)
|
||||||
|
self._setObject(oid, da)
|
||||||
|
|
||||||
|
security.declareProtected("View", "change_admin_user")
|
||||||
|
|
||||||
|
def change_admin_user(self, password, REQUEST=None):
|
||||||
|
"""Change password of admin user"""
|
||||||
|
# note: controle sur le role et non pas sur une permission
|
||||||
|
# (non definies au top level)
|
||||||
|
if not REQUEST.AUTHENTICATED_USER.has_role("Manager"):
|
||||||
|
log("user %s is not Manager" % REQUEST.AUTHENTICATED_USER)
|
||||||
|
log("roles=%s" % REQUEST.AUTHENTICATED_USER.getRolesInContext(self))
|
||||||
|
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
||||||
|
log("trying to change admin password")
|
||||||
|
# 1-- check strong password
|
||||||
|
if not sco_users.is_valid_password(password):
|
||||||
|
log("refusing weak password")
|
||||||
|
return REQUEST.RESPONSE.redirect(
|
||||||
|
"change_admin_user_form?message=Mot%20de%20passe%20trop%20simple,%20recommencez"
|
||||||
|
)
|
||||||
|
# 2-- change password for admin user
|
||||||
|
username = "admin"
|
||||||
|
acl_users = self.aq_parent.acl_users
|
||||||
|
user = acl_users.getUser(username)
|
||||||
|
r = acl_users._changeUser(
|
||||||
|
username, password, password, user.roles, user.domains
|
||||||
|
)
|
||||||
|
if not r:
|
||||||
|
# OK, set property to indicate we changed the password
|
||||||
|
log("admin password changed successfully")
|
||||||
|
self.manage_changeProperties(admin_password_initialized="1")
|
||||||
|
return r or REQUEST.RESPONSE.redirect("index_html")
|
||||||
|
|
||||||
|
security.declareProtected("View", "change_admin_user_form")
|
||||||
|
|
||||||
|
def change_admin_user_form(self, message="", REQUEST=None):
|
||||||
|
"""Form allowing to change the ScoDoc admin password"""
|
||||||
|
# note: controle sur le role et non pas sur une permission
|
||||||
|
# (non definies au top level)
|
||||||
|
if not REQUEST.AUTHENTICATED_USER.has_role("Manager"):
|
||||||
|
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
||||||
|
H = [
|
||||||
|
self.scodoc_top_html_header(
|
||||||
|
REQUEST, page_title="ScoDoc: changement mot de passe"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if message:
|
||||||
|
H.append('<div id="message">%s</div>' % message)
|
||||||
|
H.append(
|
||||||
|
"""<h2>Changement du mot de passe administrateur (utilisateur admin)</h2>
|
||||||
|
<p>
|
||||||
|
<form action="change_admin_user" method="post"><table>
|
||||||
|
<tr><td>Nouveau mot de passe:</td><td><input type="password" size="14" name="password"/></td></tr>
|
||||||
|
<tr><td>Confirmation: </td><td><input type="password" size="14" name="password2" /></td></tr>
|
||||||
|
</table>
|
||||||
|
<input type="submit" value="Changer">
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
H.append("""</body></html>""")
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
security.declareProtected("View", "list_depts")
|
||||||
|
|
||||||
|
def list_depts(self, viewable=True, format=None, REQUEST=None):
|
||||||
|
"""List departments
|
||||||
|
If viewable, list only depts viewable the current user.
|
||||||
|
"""
|
||||||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||||||
|
viewable = int(viewable)
|
||||||
|
return scu.sendResult(
|
||||||
|
REQUEST,
|
||||||
|
[
|
||||||
|
d.id
|
||||||
|
for d in self._list_depts()
|
||||||
|
if (not viewable) or authuser.has_permission(ScoView, d.Scolarite)
|
||||||
|
],
|
||||||
|
name="depts",
|
||||||
|
format=format,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _list_depts(self, REQUEST=None): # not published
|
||||||
|
# List departments folders
|
||||||
|
# (returns a list of Zope folders containing a ZScolar instance)
|
||||||
|
folders = self.objectValues("Folder")
|
||||||
|
# select folders with Scolarite object:
|
||||||
|
r = []
|
||||||
|
for folder in folders:
|
||||||
|
try:
|
||||||
|
_ = folder.Scolarite
|
||||||
|
r.append(folder)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return r
|
||||||
|
|
||||||
|
security.declareProtected("View", "create_dept")
|
||||||
|
|
||||||
|
def create_dept(self, REQUEST=None, DeptId="", pass2=False):
|
||||||
|
"""Creation (ajout) d'un site departement
|
||||||
|
(instance ZScolar + dossier la contenant)
|
||||||
|
"""
|
||||||
|
e = self._check_admin_perm(REQUEST)
|
||||||
|
if e:
|
||||||
|
return e
|
||||||
|
|
||||||
|
if not DeptId:
|
||||||
|
raise ValueError("nom de departement invalide")
|
||||||
|
if not pass2:
|
||||||
|
# 1- Creation de repertoire Dept
|
||||||
|
log("creating Zope folder " + DeptId)
|
||||||
|
add_method = self.manage_addProduct["OFSP"].manage_addFolder
|
||||||
|
add_method(DeptId, title="Site dept. " + DeptId)
|
||||||
|
|
||||||
|
DeptFolder = self[DeptId]
|
||||||
|
|
||||||
|
if not pass2:
|
||||||
|
# 2- Creation du repertoire Fotos
|
||||||
|
log("creating Zope folder %s/Fotos" % DeptId)
|
||||||
|
add_method = DeptFolder.manage_addProduct["OFSP"].manage_addFolder
|
||||||
|
add_method("Fotos", title="Photos identites " + DeptId)
|
||||||
|
|
||||||
|
# 3- Creation instance ScoDoc
|
||||||
|
log("creating Zope ZScolar instance")
|
||||||
|
add_method = DeptFolder.manage_addProduct["ScoDoc"].manage_addZScolarForm
|
||||||
|
return add_method(DeptId, REQUEST=REQUEST)
|
||||||
|
|
||||||
|
security.declareProtected("View", "delete_dept")
|
||||||
|
|
||||||
|
def delete_dept(self, REQUEST=None, DeptId="", force=False):
|
||||||
|
"""Supprime un departement (de Zope seulement, ne touche pas la BD)"""
|
||||||
|
e = self._check_admin_perm(REQUEST)
|
||||||
|
if e:
|
||||||
|
return e
|
||||||
|
|
||||||
|
if not force and DeptId not in [x.id for x in self._list_depts()]:
|
||||||
|
raise ValueError("nom de departement invalide")
|
||||||
|
|
||||||
|
self.manage_delObjects(ids=[DeptId])
|
||||||
|
|
||||||
|
return (
|
||||||
|
"<p>Département "
|
||||||
|
+ DeptId
|
||||||
|
+ """ supprimé du serveur web (la base de données n'est pas affectée)!</p><p><a href="/ScoDoc">Continuer</a></p>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
_top_level_css = """
|
||||||
|
<style type="text/css">
|
||||||
|
</style>"""
|
||||||
|
|
||||||
|
_html_begin = """<?xml version="1.0" encoding="%(encoding)s"?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>%(page_title)s</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
|
||||||
|
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||||
|
<meta name="LANG" content="fr" />
|
||||||
|
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||||
|
|
||||||
|
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />
|
||||||
|
|
||||||
|
<link href="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/sorttable.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.onload=function(){enableTooltips("gtrcontent")};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
|
||||||
|
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||||
|
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||||
|
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||||
|
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/etud_info.js"></script>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def scodoc_top_html_header(self, REQUEST, page_title="ScoDoc"):
|
||||||
|
H = [
|
||||||
|
self._html_begin
|
||||||
|
% {"page_title": "ScoDoc: bienvenue", "encoding": scu.SCO_ENCODING},
|
||||||
|
self._top_level_css,
|
||||||
|
"""</head><body class="gtrcontent" id="gtrcontent">""",
|
||||||
|
scu.CUSTOM_HTML_HEADER_CNX,
|
||||||
|
]
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
security.declareProtected("View", "index_html")
|
||||||
|
|
||||||
|
def index_html(self, REQUEST=None, message=None):
|
||||||
|
"""Top level page for ScoDoc"""
|
||||||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||||||
|
deptList = self._list_depts()
|
||||||
|
self._fix_users_folder() # fix our exUserFolder
|
||||||
|
isAdmin = not self._check_admin_perm(REQUEST)
|
||||||
|
try:
|
||||||
|
admin_password_initialized = self.admin_password_initialized
|
||||||
|
except:
|
||||||
|
admin_password_initialized = "0"
|
||||||
|
if isAdmin and admin_password_initialized != "1":
|
||||||
|
return REQUEST.RESPONSE.redirect(
|
||||||
|
"ScoDoc/change_admin_user_form?message=Le%20mot%20de%20passe%20administrateur%20doit%20etre%20change%20!"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Si l'URL indique que l'on est dans un folder, affiche page login du departement
|
||||||
|
try:
|
||||||
|
deptfoldername = REQUEST.URL0.split("ScoDoc")[1].split("/")[1]
|
||||||
|
if deptfoldername in [x.id for x in self._list_depts()]:
|
||||||
|
return self.index_dept(deptfoldername=deptfoldername, REQUEST=REQUEST)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
H = [
|
||||||
|
self.scodoc_top_html_header(REQUEST, page_title="ScoDoc: bienvenue"),
|
||||||
|
self._check_users_folder(REQUEST=REQUEST), # ensure setup is done
|
||||||
|
]
|
||||||
|
if message:
|
||||||
|
H.append('<div id="message">%s</div>' % message)
|
||||||
|
|
||||||
|
if isAdmin and not message:
|
||||||
|
H.append('<div id="message">Attention: connecté comme administrateur</div>')
|
||||||
|
|
||||||
|
H.append(
|
||||||
|
"""
|
||||||
|
<div class="maindiv">
|
||||||
|
<h2>ScoDoc: gestion scolarité</h2>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
if authuser.has_role("Authenticated"):
|
||||||
|
H.append(
|
||||||
|
"""<p>Bonjour <font color="red"><b>%s</b></font>.</p>""" % str(authuser)
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
"""<p>N'oubliez pas de vous <a href="acl_users/logout">déconnecter</a> après usage.</p>"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(
|
||||||
|
"""<p>Ce site est <font color="red"><b>réservé au personnel autorisé</b></font></p>"""
|
||||||
|
)
|
||||||
|
H.append(self.authentication_form(destination="."))
|
||||||
|
|
||||||
|
if not deptList:
|
||||||
|
H.append("<em>aucun département existant !</em>")
|
||||||
|
# si pas de dept et pas admin, propose lien pour loger admin
|
||||||
|
if not isAdmin:
|
||||||
|
H.append(
|
||||||
|
"""<p><a href="/force_admin_authentication">Identifiez vous comme administrateur</a> (au début: nom 'admin', mot de passe 'scodoc')</p>"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append('<ul class="main">')
|
||||||
|
if isAdmin:
|
||||||
|
dest_folder = "/Scolarite"
|
||||||
|
else:
|
||||||
|
dest_folder = ""
|
||||||
|
for deptFolder in self._list_depts():
|
||||||
|
if authuser.has_permission(ScoView, deptFolder.Scolarite):
|
||||||
|
link_cls = "link_accessible"
|
||||||
|
else:
|
||||||
|
link_cls = "link_unauthorized"
|
||||||
|
# Essai de recuperer le nom du departement dans ses preferences
|
||||||
|
try:
|
||||||
|
DeptName = (
|
||||||
|
deptFolder.Scolarite.get_preference("DeptName") or deptFolder.id
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
DeptName = deptFolder.id
|
||||||
|
H.append(
|
||||||
|
'<li><a class="stdlink %s" href="%s%s">Département %s</a>'
|
||||||
|
% (link_cls, deptFolder.absolute_url(), dest_folder, DeptName)
|
||||||
|
)
|
||||||
|
# check if roles are initialized in this depts, and do it if necessary
|
||||||
|
if deptFolder.Scolarite.roles_initialized == "0":
|
||||||
|
if isAdmin:
|
||||||
|
deptFolder.Scolarite._setup_initial_roles_and_permissions()
|
||||||
|
else:
|
||||||
|
H.append(" (non initialisé, connectez vous comme admin)")
|
||||||
|
H.append("</li>")
|
||||||
|
H.append("</ul>")
|
||||||
|
# Recherche etudiant
|
||||||
|
H.append(sco_find_etud.form_search_etud_in_accessible_depts(self, REQUEST))
|
||||||
|
|
||||||
|
if isAdmin:
|
||||||
|
H.append('<p><a href="scodoc_admin">Administration de ScoDoc</a></p>')
|
||||||
|
else:
|
||||||
|
H.append(
|
||||||
|
'<p><a href="%s/force_admin_authentication">Se connecter comme administrateur</a></p>'
|
||||||
|
% REQUEST.BASE0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Lien expérimental temporaire:
|
||||||
|
H.append(
|
||||||
|
'<p><a href="/ScoDoc/static/mobile">Version mobile (expérimentale, à vos risques et périls)</a></p>'
|
||||||
|
)
|
||||||
|
|
||||||
|
H.append(
|
||||||
|
"""
|
||||||
|
<div id="scodoc_attribution">
|
||||||
|
<p><a href="%s">ScoDoc</a> est un logiciel libre de suivi de la scolarité des étudiants conçu par
|
||||||
|
E. Viennet (Université Paris 13).</p>
|
||||||
|
</div>
|
||||||
|
</div>"""
|
||||||
|
% (scu.SCO_WEBSITE,)
|
||||||
|
)
|
||||||
|
|
||||||
|
H.append("""</body></html>""")
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
def authentication_form(self, destination=""):
|
||||||
|
"""html snippet for authentication"""
|
||||||
|
return (
|
||||||
|
"""<!-- authentication_form -->
|
||||||
|
<form action="doLogin" method="post">
|
||||||
|
<input type="hidden" name="destination" value="%s"/>
|
||||||
|
<p>
|
||||||
|
<table border="0" cellpadding="3">
|
||||||
|
<tr>
|
||||||
|
<td><b>Nom:</b></td>
|
||||||
|
<td><input id="name" type="text" name="__ac_name" size="20"/></td>
|
||||||
|
</tr><tr>
|
||||||
|
<td><b>Mot de passe:</b></td>
|
||||||
|
<td><input id="password" type="password" name="__ac_password" size="20"/></td>
|
||||||
|
<td><input id="submit" name="submit" type="submit" value="OK"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
</form>"""
|
||||||
|
% destination
|
||||||
|
)
|
||||||
|
|
||||||
|
security.declareProtected("View", "index_dept")
|
||||||
|
|
||||||
|
def index_dept(self, deptfoldername="", REQUEST=None):
|
||||||
|
"""Page d'accueil departement"""
|
||||||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||||||
|
try:
|
||||||
|
dept = getattr(self, deptfoldername)
|
||||||
|
if authuser.has_permission(ScoView, dept):
|
||||||
|
return REQUEST.RESPONSE.redirect("ScoDoc/%s/Scolarite" % deptfoldername)
|
||||||
|
except:
|
||||||
|
log(
|
||||||
|
"*** problem in index_dept (%s) user=%s"
|
||||||
|
% (deptfoldername, str(authuser))
|
||||||
|
)
|
||||||
|
|
||||||
|
H = [
|
||||||
|
self.standard_html_header(REQUEST),
|
||||||
|
"""<div style="margin: 1em;">
|
||||||
|
<h2>Scolarité du département %s</h2>"""
|
||||||
|
% deptfoldername,
|
||||||
|
"""<p>Ce site est
|
||||||
|
<font color="#FF0000"><b>réservé au personnel du département</b></font>.
|
||||||
|
</p>""",
|
||||||
|
self.authentication_form(destination="Scolarite"),
|
||||||
|
"""
|
||||||
|
<p>Pour quitter, <a href="acl_users/logout">logout</a></p>
|
||||||
|
<p><a href="%s">Retour à l'accueil</a></p>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
% self.ScoDocURL(),
|
||||||
|
self.standard_html_footer(REQUEST),
|
||||||
|
]
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
security.declareProtected("View", "doLogin")
|
||||||
|
|
||||||
|
def doLogin(self, REQUEST=None, destination=None):
|
||||||
|
"redirect to destination after login"
|
||||||
|
if destination:
|
||||||
|
return REQUEST.RESPONSE.redirect(destination)
|
||||||
|
|
||||||
|
security.declareProtected("View", "docLogin")
|
||||||
|
docLogin = DTMLFile("dtml/docLogin", globals())
|
||||||
|
security.declareProtected("View", "docLogout")
|
||||||
|
docLogout = DTMLFile("dtml/docLogout", globals())
|
||||||
|
|
||||||
|
security.declareProtected("View", "query_string_to_form_inputs")
|
||||||
|
|
||||||
|
def query_string_to_form_inputs(self, query_string=""):
|
||||||
|
"""Return html snippet representing the query string as POST form hidden inputs.
|
||||||
|
This is useful in conjonction with exUserfolder to correctly redirect the response
|
||||||
|
after authentication.
|
||||||
|
"""
|
||||||
|
H = []
|
||||||
|
for a in query_string.split("&"):
|
||||||
|
if a:
|
||||||
|
nv = a.split("=")
|
||||||
|
if len(nv) == 2:
|
||||||
|
name, value = nv
|
||||||
|
H.append(
|
||||||
|
'<input type="hidden" name="'
|
||||||
|
+ name
|
||||||
|
+ '" value="'
|
||||||
|
+ value
|
||||||
|
+ '"/>'
|
||||||
|
)
|
||||||
|
|
||||||
|
return "<!-- query string -->\n" + "\n".join(H)
|
||||||
|
|
||||||
|
security.declareProtected("View", "standard_html_header")
|
||||||
|
|
||||||
|
def standard_html_header(self, REQUEST=None):
|
||||||
|
"""Standard HTML header for pages outside depts"""
|
||||||
|
# not used in ZScolar, see sco_header
|
||||||
|
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||||
|
<html><head>
|
||||||
|
<title>ScoDoc: accueil</title>
|
||||||
|
<META http-equiv="Content-Type" content="text/html; charset=%s">
|
||||||
|
<META http-equiv="Content-Style-Type" content="text/css">
|
||||||
|
<META name="LANG" content="fr">
|
||||||
|
<META name="DESCRIPTION" content="ScoDoc: gestion scolarite">
|
||||||
|
|
||||||
|
<link HREF="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css"/>
|
||||||
|
|
||||||
|
</head><body>%s""" % (
|
||||||
|
scu.SCO_ENCODING,
|
||||||
|
scu.CUSTOM_HTML_HEADER_CNX,
|
||||||
|
)
|
||||||
|
|
||||||
|
security.declareProtected("View", "standard_html_footer")
|
||||||
|
|
||||||
|
def standard_html_footer(self, REQUEST=None):
|
||||||
|
"""Le pied de page HTML de la page d'accueil."""
|
||||||
|
return """<p class="footer">
|
||||||
|
Problème de connexion (identifiant, mot de passe): <em>contacter votre responsable ou chef de département</em>.</p>
|
||||||
|
<p>Problèmes et suggestions sur le logiciel: <a href="mailto:%s">%s</a></p>
|
||||||
|
<p><em>ScoDoc est un logiciel libre développé par Emmanuel Viennet.</em></p>
|
||||||
|
</body></html>""" % (
|
||||||
|
scu.SCO_USERS_LIST,
|
||||||
|
scu.SCO_USERS_LIST,
|
||||||
|
)
|
||||||
|
|
||||||
|
# sendEmail is not used through the web
|
||||||
|
def sendEmail(self, msg):
|
||||||
|
# sends an email to the address using the mailhost, if there is one
|
||||||
|
try:
|
||||||
|
mail_host = self.MailHost
|
||||||
|
except:
|
||||||
|
log("warning: sendEmail: no MailHost found !")
|
||||||
|
return
|
||||||
|
# a failed notification shouldn't cause a Zope error on a site.
|
||||||
|
try:
|
||||||
|
mail_host.send(msg.as_string())
|
||||||
|
log("sendEmail: ok")
|
||||||
|
except Exception as e:
|
||||||
|
log("sendEmail: exception while sending message")
|
||||||
|
log(e)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sendEmailFromException(self, msg):
|
||||||
|
# Send email by hand, as it seems to be not possible to use Zope Mail Host
|
||||||
|
# from an exception handler (see https://bugs.launchpad.net/zope2/+bug/246748)
|
||||||
|
log("sendEmailFromException")
|
||||||
|
try:
|
||||||
|
p = os.popen("sendmail -t", "w") # old brute force method
|
||||||
|
p.write(msg.as_string())
|
||||||
|
exitcode = p.close()
|
||||||
|
if exitcode:
|
||||||
|
log("sendmail exit code: %s" % exitcode)
|
||||||
|
except:
|
||||||
|
log("an exception occurred sending mail")
|
||||||
|
|
||||||
|
security.declareProtected("View", "standard_error_message")
|
||||||
|
|
||||||
|
def standard_error_message(
|
||||||
|
self,
|
||||||
|
error_value=None,
|
||||||
|
error_message=None, # unused ?
|
||||||
|
error_type=None,
|
||||||
|
error_traceback=None,
|
||||||
|
error_tb=None,
|
||||||
|
**kv
|
||||||
|
):
|
||||||
|
"Recuperation des exceptions Zope"
|
||||||
|
# neat (or should I say dirty ?) hack to get REQUEST
|
||||||
|
# in fact, our caller (probably SimpleItem.py) has the REQUEST variable
|
||||||
|
# that we'd like to use for our logs, but does not pass it as an argument.
|
||||||
|
try:
|
||||||
|
frame = inspect.currentframe()
|
||||||
|
REQUEST = frame.f_back.f_locals["REQUEST"]
|
||||||
|
except:
|
||||||
|
REQUEST = {}
|
||||||
|
|
||||||
|
# Authentication uses exceptions, pass them up
|
||||||
|
HTTP_X_FORWARDED_FOR = REQUEST.get("HTTP_X_FORWARDED_FOR", "")
|
||||||
|
if error_type == "LoginRequired":
|
||||||
|
log("LoginRequired from %s" % HTTP_X_FORWARDED_FOR)
|
||||||
|
self.login_page = error_value
|
||||||
|
return error_value
|
||||||
|
elif error_type == "Unauthorized":
|
||||||
|
log("Unauthorized from %s" % HTTP_X_FORWARDED_FOR)
|
||||||
|
return self.acl_users.docLogin(self, REQUEST=REQUEST)
|
||||||
|
|
||||||
|
log("exception caught: %s" % error_type)
|
||||||
|
log(traceback.format_exc())
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"error_type": error_type,
|
||||||
|
"error_value": error_value,
|
||||||
|
"error_tb": error_tb,
|
||||||
|
"sco_exc_mail": scu.SCO_EXC_MAIL,
|
||||||
|
"sco_dev_mail": scu.SCO_DEV_MAIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
if error_type == "ScoGenError":
|
||||||
|
return "<p>" + str(error_value) + "</p>"
|
||||||
|
elif error_type in ("ScoValueError", "FormatError"):
|
||||||
|
# Not a bug, presents a gentle message to the user:
|
||||||
|
H = [
|
||||||
|
self.standard_html_header(REQUEST),
|
||||||
|
"""<h2>Erreur !</h2><p>%s</p>""" % error_value,
|
||||||
|
]
|
||||||
|
if error_value.dest_url:
|
||||||
|
H.append('<p><a href="%s">Continuer</a></p>' % error_value.dest_url)
|
||||||
|
H.append(self.standard_html_footer(REQUEST))
|
||||||
|
return "\n".join(H)
|
||||||
|
else: # Other exceptions, try carefully to build an error page...
|
||||||
|
# log('exc A')
|
||||||
|
H = []
|
||||||
|
try:
|
||||||
|
H.append(self.standard_html_header(REQUEST))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
H.append(
|
||||||
|
"""<table border="0" width="100%%"><tr valign="top">
|
||||||
|
<td width="10%%" align="center"></td>
|
||||||
|
<td width="90%%"><h2>Erreur !</h2>
|
||||||
|
<p>Une erreur est survenue</p>
|
||||||
|
<p>
|
||||||
|
<strong>Error Type: %(error_type)s</strong><br>
|
||||||
|
<strong>Error Value: %(error_value)s</strong><br>
|
||||||
|
</p>
|
||||||
|
<hr noshade>
|
||||||
|
<p>L'URL est peut-etre incorrecte ?</p>
|
||||||
|
|
||||||
|
<p>Si l'erreur persiste, contactez Emmanuel Viennet:
|
||||||
|
<a href="mailto:%(sco_dev_mail)s">%(sco_dev_mail)s</a>
|
||||||
|
en copiant ce message d'erreur et le contenu du cadre bleu ci-dessous si possible.
|
||||||
|
</p>
|
||||||
|
</td></tr>
|
||||||
|
</table> """
|
||||||
|
% params
|
||||||
|
)
|
||||||
|
# display error traceback (? may open a security risk via xss attack ?)
|
||||||
|
# log('exc B')
|
||||||
|
params["txt_html"] = self._report_request(REQUEST, fmt="html")
|
||||||
|
H.append(
|
||||||
|
"""<h4 class="scodoc">Zope Traceback (à envoyer par mail à <a href="mailto:%(sco_dev_mail)s">%(sco_dev_mail)s</a>)</h4><div style="background-color: rgb(153,153,204); border: 1px;">
|
||||||
|
%(error_tb)s
|
||||||
|
<p><b>Informations:</b><br/>
|
||||||
|
%(txt_html)s
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Merci de votre patience !</p>
|
||||||
|
"""
|
||||||
|
% params
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
H.append(self.standard_html_footer(REQUEST))
|
||||||
|
except:
|
||||||
|
log("no footer found for error page")
|
||||||
|
pass
|
||||||
|
|
||||||
|
# --- Mail:
|
||||||
|
params["error_traceback_txt"] = scu.scodoc_html2txt(error_tb)
|
||||||
|
txt = (
|
||||||
|
"""
|
||||||
|
ErrorType: %(error_type)s
|
||||||
|
|
||||||
|
%(error_traceback_txt)s
|
||||||
|
"""
|
||||||
|
% params
|
||||||
|
)
|
||||||
|
|
||||||
|
self.send_debug_alert(txt, REQUEST=REQUEST)
|
||||||
|
# ---
|
||||||
|
log("done processing exception")
|
||||||
|
# log( '\n page=\n' + '\n'.join(H) )
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
def _report_request(self, REQUEST, fmt="txt"):
|
||||||
|
"""string describing current request for bug reports"""
|
||||||
|
QUERY_STRING = REQUEST.get("QUERY_STRING", "")
|
||||||
|
if QUERY_STRING:
|
||||||
|
QUERY_STRING = "?" + QUERY_STRING
|
||||||
|
if fmt == "txt":
|
||||||
|
REFERER = REQUEST.get("HTTP_REFERER", "")
|
||||||
|
HTTP_USER_AGENT = REQUEST.get("HTTP_USER_AGENT", "")
|
||||||
|
else:
|
||||||
|
REFERER = "na"
|
||||||
|
HTTP_USER_AGENT = "na"
|
||||||
|
|
||||||
|
params = dict(
|
||||||
|
AUTHENTICATED_USER=REQUEST.get("AUTHENTICATED_USER", ""),
|
||||||
|
dt=time.asctime(),
|
||||||
|
URL=REQUEST.get("URL", ""),
|
||||||
|
QUERY_STRING=QUERY_STRING,
|
||||||
|
METHOD=REQUEST.get("REQUEST_METHOD", ""),
|
||||||
|
REFERER=REFERER,
|
||||||
|
HTTP_USER_AGENT=HTTP_USER_AGENT,
|
||||||
|
form=REQUEST.get("form", ""),
|
||||||
|
HTTP_X_FORWARDED_FOR=REQUEST.get("HTTP_X_FORWARDED_FOR", ""),
|
||||||
|
svn_version=scu.get_svn_version(self.file_path),
|
||||||
|
SCOVERSION=VERSION.SCOVERSION,
|
||||||
|
)
|
||||||
|
txt = (
|
||||||
|
"""
|
||||||
|
Version: %(SCOVERSION)s
|
||||||
|
User: %(AUTHENTICATED_USER)s
|
||||||
|
Date: %(dt)s
|
||||||
|
URL: %(URL)s%(QUERY_STRING)s
|
||||||
|
Method: %(METHOD)s
|
||||||
|
|
||||||
|
REFERER: %(REFERER)s
|
||||||
|
Form: %(form)s
|
||||||
|
Origin: %(HTTP_X_FORWARDED_FOR)s
|
||||||
|
Agent: %(HTTP_USER_AGENT)s
|
||||||
|
|
||||||
|
subversion: %(svn_version)s
|
||||||
|
"""
|
||||||
|
% params
|
||||||
|
)
|
||||||
|
if fmt == "html":
|
||||||
|
txt = txt.replace("\n", "<br/>")
|
||||||
|
return txt
|
||||||
|
|
||||||
|
security.declareProtected(
|
||||||
|
ScoSuperAdmin, "send_debug_alert"
|
||||||
|
) # not called through the web
|
||||||
|
|
||||||
|
def send_debug_alert(self, txt, REQUEST=None):
|
||||||
|
"""Send an alert email (bug report) to ScoDoc developpers"""
|
||||||
|
if not scu.SCO_EXC_MAIL:
|
||||||
|
log("send_debug_alert: email disabled")
|
||||||
|
return
|
||||||
|
if REQUEST:
|
||||||
|
txt = self._report_request(REQUEST) + txt
|
||||||
|
URL = REQUEST.get("URL", "")
|
||||||
|
else:
|
||||||
|
URL = "send_debug_alert"
|
||||||
|
msg = MIMEMultipart()
|
||||||
|
subj = Header("[scodoc] exc %s" % URL, scu.SCO_ENCODING)
|
||||||
|
msg["Subject"] = subj
|
||||||
|
recipients = [scu.SCO_EXC_MAIL]
|
||||||
|
msg["To"] = " ,".join(recipients)
|
||||||
|
msg["From"] = "scodoc-alert"
|
||||||
|
msg.epilogue = ""
|
||||||
|
msg.attach(MIMEText(txt, "plain", scu.SCO_ENCODING))
|
||||||
|
self.sendEmailFromException(msg)
|
||||||
|
log("Sent mail alert:\n" + txt)
|
||||||
|
|
||||||
|
security.declareProtected("View", "scodoc_admin")
|
||||||
|
|
||||||
|
def scodoc_admin(self, REQUEST=None):
|
||||||
|
"""Page Operations d'administration"""
|
||||||
|
e = self._check_admin_perm(REQUEST)
|
||||||
|
if e:
|
||||||
|
return e
|
||||||
|
|
||||||
|
H = [
|
||||||
|
self.scodoc_top_html_header(REQUEST, page_title="ScoDoc: bienvenue"),
|
||||||
|
"""
|
||||||
|
<h3>Administration ScoDoc</h3>
|
||||||
|
|
||||||
|
<p><a href="change_admin_user_form">changer le mot de passe super-administrateur</a></p>
|
||||||
|
<p><a href="%s">retour à la page d'accueil</a></p>
|
||||||
|
|
||||||
|
<h4 class="scodoc">Création d'un département</h4>
|
||||||
|
<p class="help_important">Le département doit avoir été créé au préalable sur le serveur en utilisant le script
|
||||||
|
<tt>create_dept.sh</tt> (à lancer comme <tt>root</tt> dans le répertoire <tt>config</tt> de ScoDoc).
|
||||||
|
</p>"""
|
||||||
|
% self.absolute_url(),
|
||||||
|
]
|
||||||
|
|
||||||
|
deptList = [x.id for x in self._list_depts()] # definis dans Zope
|
||||||
|
deptIds = set(self._list_depts_ids()) # definis sur le filesystem
|
||||||
|
existingDepts = set(deptList)
|
||||||
|
addableDepts = deptIds - existingDepts
|
||||||
|
|
||||||
|
if not addableDepts:
|
||||||
|
# aucun departement defini: aide utilisateur
|
||||||
|
H.append("<p>Aucun département à ajouter !</p>")
|
||||||
|
else:
|
||||||
|
H.append("""<form action="create_dept"><select name="DeptId"/>""")
|
||||||
|
for deptId in addableDepts:
|
||||||
|
H.append("""<option value="%s">%s</option>""" % (deptId, deptId))
|
||||||
|
H.append(
|
||||||
|
"""</select>
|
||||||
|
<input type="submit" value="Créer département">
|
||||||
|
</form>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if deptList:
|
||||||
|
H.append(
|
||||||
|
"""
|
||||||
|
<h4 class="scodoc">Suppression d'un département</h4>
|
||||||
|
<p>Ceci permet de supprimer le site web associé à un département, mais n'affecte pas la base de données
|
||||||
|
(le site peut donc être recréé sans perte de données).
|
||||||
|
</p>
|
||||||
|
<form action="delete_dept">
|
||||||
|
<select name="DeptId">
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for deptFolder in self._list_depts():
|
||||||
|
H.append(
|
||||||
|
'<option value="%s">%s</option>' % (deptFolder.id, deptFolder.id)
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
"""</select>
|
||||||
|
<input type="submit" value="Supprimer département">
|
||||||
|
|
||||||
|
</form>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
H.append("""</body></html>""")
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
def _list_depts_ids(self):
|
||||||
|
"""Liste de id de departements definis par create_dept.sh
|
||||||
|
(fichiers depts/*.cfg)
|
||||||
|
"""
|
||||||
|
filenames = glob.glob(scu.SCODOC_VAR_DIR + "/config/depts/*.cfg")
|
||||||
|
ids = [os.path.split(os.path.splitext(f)[0])[1] for f in filenames]
|
||||||
|
return ids
|
||||||
|
|
||||||
|
security.declareProtected("View", "http_expiration_date")
|
||||||
|
|
||||||
|
def http_expiration_date(self):
|
||||||
|
"http expiration date for cachable elements (css, ...)"
|
||||||
|
d = datetime.timedelta(minutes=10)
|
||||||
|
return (datetime.datetime.utcnow() + d).strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||||
|
|
||||||
|
security.declareProtected("View", "get_etud_dept")
|
||||||
|
|
||||||
|
def get_etud_dept(self, REQUEST=None):
|
||||||
|
"""Returns the dept id (eg "GEII") of an etud (identified by etudid, INE or NIP in REQUEST).
|
||||||
|
Warning: This function is inefficient and its result should be cached.
|
||||||
|
"""
|
||||||
|
depts = self._list_depts()
|
||||||
|
depts_etud = [] # liste des depts où l'etud est defini
|
||||||
|
for dept in depts:
|
||||||
|
etuds = dept.Scolarite.getEtudInfo(REQUEST=REQUEST)
|
||||||
|
if etuds:
|
||||||
|
depts_etud.append((dept, etuds))
|
||||||
|
if not depts_etud:
|
||||||
|
return "" # not found
|
||||||
|
elif len(depts_etud) == 1:
|
||||||
|
return depts_etud[0][0].id
|
||||||
|
# inscriptions dans plusieurs departements: cherche la plus recente
|
||||||
|
last_dept = None
|
||||||
|
last_date = None
|
||||||
|
for (dept, etuds) in depts_etud:
|
||||||
|
dept.Scolarite.fillEtudsInfo(etuds)
|
||||||
|
etud = etuds[0]
|
||||||
|
if etud["sems"]:
|
||||||
|
if (not last_date) or (etud["sems"][0]["date_fin_iso"] > last_date):
|
||||||
|
last_date = etud["sems"][0]["date_fin_iso"]
|
||||||
|
last_dept = dept
|
||||||
|
if not last_dept:
|
||||||
|
# est present dans plusieurs semestres mais inscrit dans aucun
|
||||||
|
return depts_etud[0][0]
|
||||||
|
return last_dept.id
|
||||||
|
|
||||||
|
security.declareProtected("View", "table_etud_in_accessible_depts")
|
||||||
|
table_etud_in_accessible_depts = sco_find_etud.table_etud_in_accessible_depts
|
||||||
|
|
||||||
|
security.declareProtected("View", "search_inscr_etud_by_nip")
|
||||||
|
search_inscr_etud_by_nip = sco_find_etud.search_inscr_etud_by_nip
|
||||||
|
|
||||||
|
|
||||||
|
def manage_addZScoDoc(self, id="ScoDoc", title="Site ScoDoc", REQUEST=None):
|
||||||
|
"Add a ZScoDoc instance to a folder."
|
||||||
|
log("============== creating a new ScoDoc instance =============")
|
||||||
|
zscodoc = ZScoDoc(
|
||||||
|
id, title
|
||||||
|
) # ne cree (presque rien), tout se passe lors du 1er accès
|
||||||
|
self._setObject(id, zscodoc)
|
||||||
|
if REQUEST is not None:
|
||||||
|
REQUEST.RESPONSE.redirect("/ScoDoc/manage_workspace")
|
||||||
|
return id
|
1322
ZScoUsers.py
Normal file
2783
ZScolar.py
Normal file
5
ZopeProducts/README
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
Produits Zope2 anciens et adaptes pour ScoDoc
|
||||||
|
|
||||||
|
E. Viennet 2013
|
||||||
|
|
372
ZopeProducts/ZPsycopgDA/DA.py
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
# ZPsycopgDA/DA.py - ZPsycopgDA Zope product: Database Connection
|
||||||
|
#
|
||||||
|
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
||||||
|
#
|
||||||
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# psycopg2 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 Lesser General Public
|
||||||
|
# License for more details.
|
||||||
|
|
||||||
|
# Import modules needed by _psycopg to allow tools like py2exe to do
|
||||||
|
# their work without bothering about the module dependencies.
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import db
|
||||||
|
import re
|
||||||
|
|
||||||
|
import Acquisition
|
||||||
|
import Shared.DC.ZRDB.Connection
|
||||||
|
|
||||||
|
from db import DB
|
||||||
|
from Globals import HTMLFile
|
||||||
|
from ExtensionClass import Base
|
||||||
|
from App.Dialogs import MessageDialog
|
||||||
|
from DateTime import DateTime
|
||||||
|
|
||||||
|
# ImageFile is deprecated in Zope >= 2.9
|
||||||
|
try:
|
||||||
|
from App.ImageFile import ImageFile
|
||||||
|
except ImportError:
|
||||||
|
# Zope < 2.9. If PIL's installed with a .pth file, we're probably
|
||||||
|
# hosed.
|
||||||
|
from ImageFile import ImageFile
|
||||||
|
|
||||||
|
# import psycopg and functions/singletons needed for date/time conversions
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
from psycopg2 import NUMBER, STRING, ROWID, DATETIME
|
||||||
|
from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE
|
||||||
|
from psycopg2.extensions import TIME, INTERVAL
|
||||||
|
from psycopg2.extensions import new_type, register_type
|
||||||
|
|
||||||
|
|
||||||
|
# add a new connection to a folder
|
||||||
|
|
||||||
|
manage_addZPsycopgConnectionForm = HTMLFile('dtml/add',globals())
|
||||||
|
|
||||||
|
def manage_addZPsycopgConnection(self, id, title, connection_string,
|
||||||
|
zdatetime=None, tilevel=2,
|
||||||
|
encoding='', check=None, REQUEST=None):
|
||||||
|
"""Add a DB connection to a folder."""
|
||||||
|
self._setObject(id, Connection(id, title, connection_string,
|
||||||
|
zdatetime, check, tilevel, encoding))
|
||||||
|
if REQUEST is not None: return self.manage_main(self, REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
# the connection object
|
||||||
|
|
||||||
|
class Connection(Shared.DC.ZRDB.Connection.Connection):
|
||||||
|
"""ZPsycopg Connection."""
|
||||||
|
_isAnSQLConnection = 1
|
||||||
|
|
||||||
|
id = 'Psycopg2_database_connection'
|
||||||
|
database_type = 'Psycopg2'
|
||||||
|
meta_type = title = 'Z Psycopg 2 Database Connection'
|
||||||
|
icon = 'misc_/conn'
|
||||||
|
|
||||||
|
def __init__(self, id, title, connection_string,
|
||||||
|
zdatetime, check=None, tilevel=2, encoding='UTF-8'):
|
||||||
|
self.zdatetime = zdatetime
|
||||||
|
self.id = str(id)
|
||||||
|
self.edit(title, connection_string, zdatetime,
|
||||||
|
check=check, tilevel=tilevel, encoding=encoding)
|
||||||
|
|
||||||
|
def factory(self):
|
||||||
|
return DB
|
||||||
|
|
||||||
|
## connection parameters editing ##
|
||||||
|
|
||||||
|
def edit(self, title, connection_string,
|
||||||
|
zdatetime, check=None, tilevel=2, encoding='UTF-8'):
|
||||||
|
self.title = title
|
||||||
|
self.connection_string = connection_string
|
||||||
|
self.zdatetime = zdatetime
|
||||||
|
self.tilevel = tilevel
|
||||||
|
self.encoding = encoding
|
||||||
|
|
||||||
|
if check: self.connect(self.connection_string)
|
||||||
|
|
||||||
|
manage_properties = HTMLFile('dtml/edit', globals())
|
||||||
|
|
||||||
|
def manage_edit(self, title, connection_string,
|
||||||
|
zdatetime=None, check=None, tilevel=2, encoding='UTF-8',
|
||||||
|
REQUEST=None):
|
||||||
|
"""Edit the DB connection."""
|
||||||
|
self.edit(title, connection_string, zdatetime,
|
||||||
|
check=check, tilevel=tilevel, encoding=encoding)
|
||||||
|
if REQUEST is not None:
|
||||||
|
msg = "Connection edited."
|
||||||
|
return self.manage_main(self,REQUEST,manage_tabs_message=msg)
|
||||||
|
|
||||||
|
def connect(self, s):
|
||||||
|
try:
|
||||||
|
self._v_database_connection.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# check psycopg version and raise exception if does not match
|
||||||
|
check_psycopg_version(psycopg2.__version__)
|
||||||
|
|
||||||
|
self._v_connected = ''
|
||||||
|
dbf = self.factory()
|
||||||
|
|
||||||
|
# TODO: let the psycopg exception propagate, or not?
|
||||||
|
self._v_database_connection = dbf(
|
||||||
|
self.connection_string, self.tilevel, self.get_type_casts(), self.encoding)
|
||||||
|
self._v_database_connection.open()
|
||||||
|
self._v_connected = DateTime()
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_type_casts(self):
|
||||||
|
# note that in both cases order *is* important
|
||||||
|
if self.zdatetime:
|
||||||
|
return ZDATETIME, ZDATE, ZTIME
|
||||||
|
else:
|
||||||
|
return DATETIME, DATE, TIME
|
||||||
|
|
||||||
|
## browsing and table/column management ##
|
||||||
|
|
||||||
|
manage_options = Shared.DC.ZRDB.Connection.Connection.manage_options
|
||||||
|
# + (
|
||||||
|
# {'label': 'Browse', 'action':'manage_browse'},)
|
||||||
|
|
||||||
|
#manage_tables = HTMLFile('dtml/tables', globals())
|
||||||
|
#manage_browse = HTMLFile('dtml/browse', globals())
|
||||||
|
|
||||||
|
info = None
|
||||||
|
|
||||||
|
def table_info(self):
|
||||||
|
return self._v_database_connection.table_info()
|
||||||
|
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
if name == 'tableNamed':
|
||||||
|
if not hasattr(self, '_v_tables'): self.tpValues()
|
||||||
|
return self._v_tables.__of__(self)
|
||||||
|
raise KeyError, name
|
||||||
|
|
||||||
|
def tpValues(self):
|
||||||
|
res = []
|
||||||
|
conn = self._v_database_connection
|
||||||
|
for d in conn.tables(rdb=0):
|
||||||
|
try:
|
||||||
|
name = d['TABLE_NAME']
|
||||||
|
b = TableBrowser()
|
||||||
|
b.__name__ = name
|
||||||
|
b._d = d
|
||||||
|
b._c = c
|
||||||
|
try:
|
||||||
|
b.icon = table_icons[d['TABLE_TYPE']]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
r.append(b)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return res
|
||||||
|
|
||||||
|
def check_psycopg_version(version):
|
||||||
|
"""
|
||||||
|
Check that the psycopg version used is compatible with the zope adpter.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
m = re.match(r'\d+\.\d+(\.\d+)?', version.split(' ')[0])
|
||||||
|
tver = tuple(map(int, m.group().split('.')))
|
||||||
|
except:
|
||||||
|
raise ImportError("failed to parse psycopg version %s" % version)
|
||||||
|
|
||||||
|
if tver < (2, 4):
|
||||||
|
raise ImportError("psycopg version %s is too old" % version)
|
||||||
|
|
||||||
|
if tver in ((2,4,2), (2,4,3)):
|
||||||
|
raise ImportError("psycopg version %s is known to be buggy" % version)
|
||||||
|
|
||||||
|
|
||||||
|
## database connection registration data ##
|
||||||
|
|
||||||
|
classes = (Connection,)
|
||||||
|
|
||||||
|
meta_types = ({'name':'Z Psycopg 2 Database Connection',
|
||||||
|
'action':'manage_addZPsycopgConnectionForm'},)
|
||||||
|
|
||||||
|
folder_methods = {
|
||||||
|
'manage_addZPsycopgConnection': manage_addZPsycopgConnection,
|
||||||
|
'manage_addZPsycopgConnectionForm': manage_addZPsycopgConnectionForm}
|
||||||
|
|
||||||
|
__ac_permissions__ = (
|
||||||
|
('Add Z Psycopg Database Connections',
|
||||||
|
('manage_addZPsycopgConnectionForm', 'manage_addZPsycopgConnection')),)
|
||||||
|
|
||||||
|
# add icons
|
||||||
|
|
||||||
|
misc_={'conn': ImageFile('icons/DBAdapterFolder_icon.gif', globals())}
|
||||||
|
|
||||||
|
for icon in ('table', 'view', 'stable', 'what', 'field', 'text', 'bin',
|
||||||
|
'int', 'float', 'date', 'time', 'datetime'):
|
||||||
|
misc_[icon] = ImageFile('icons/%s.gif' % icon, globals())
|
||||||
|
|
||||||
|
|
||||||
|
## zope-specific psycopg typecasters ##
|
||||||
|
|
||||||
|
# convert an ISO timestamp string from postgres to a Zope DateTime object
|
||||||
|
def _cast_DateTime(iso, curs):
|
||||||
|
if iso:
|
||||||
|
if iso in ['-infinity', 'infinity']:
|
||||||
|
return iso
|
||||||
|
else:
|
||||||
|
return DateTime(iso)
|
||||||
|
|
||||||
|
# convert an ISO date string from postgres to a Zope DateTime object
|
||||||
|
def _cast_Date(iso, curs):
|
||||||
|
if iso:
|
||||||
|
if iso in ['-infinity', 'infinity']:
|
||||||
|
return iso
|
||||||
|
else:
|
||||||
|
return DateTime(iso)
|
||||||
|
|
||||||
|
# Convert a time string from postgres to a Zope DateTime object.
|
||||||
|
# NOTE: we set the day as today before feeding to DateTime so
|
||||||
|
# that it has the same DST settings.
|
||||||
|
def _cast_Time(iso, curs):
|
||||||
|
if iso:
|
||||||
|
if iso in ['-infinity', 'infinity']:
|
||||||
|
return iso
|
||||||
|
else:
|
||||||
|
return DateTime(time.strftime('%Y-%m-%d %H:%M:%S',
|
||||||
|
time.localtime(time.time())[:3]+
|
||||||
|
time.strptime(iso[:8], "%H:%M:%S")[3:]))
|
||||||
|
|
||||||
|
# NOTE: we don't cast intervals anymore because they are passed
|
||||||
|
# untouched to Zope.
|
||||||
|
def _cast_Interval(iso, curs):
|
||||||
|
return iso
|
||||||
|
|
||||||
|
ZDATETIME = new_type((1184, 1114), "ZDATETIME", _cast_DateTime)
|
||||||
|
ZINTERVAL = new_type((1186,), "ZINTERVAL", _cast_Interval)
|
||||||
|
ZDATE = new_type((1082,), "ZDATE", _cast_Date)
|
||||||
|
ZTIME = new_type((1083,), "ZTIME", _cast_Time)
|
||||||
|
|
||||||
|
|
||||||
|
## table browsing helpers ##
|
||||||
|
|
||||||
|
class TableBrowserCollection(Acquisition.Implicit):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Browser(Base):
|
||||||
|
def __getattr__(self, name):
|
||||||
|
try:
|
||||||
|
return self._d[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError, name
|
||||||
|
|
||||||
|
class values:
|
||||||
|
def len(self):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
try:
|
||||||
|
return self._d[i]
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self._d = self._f()
|
||||||
|
return self._d[i]
|
||||||
|
|
||||||
|
class TableBrowser(Browser, Acquisition.Implicit):
|
||||||
|
icon = 'what'
|
||||||
|
Description = check = ''
|
||||||
|
info = HTMLFile('table_info', globals())
|
||||||
|
menu = HTMLFile('table_menu', globals())
|
||||||
|
|
||||||
|
def tpValues(self):
|
||||||
|
v = values()
|
||||||
|
v._f = self.tpValues_
|
||||||
|
return v
|
||||||
|
|
||||||
|
def tpValues_(self):
|
||||||
|
r=[]
|
||||||
|
tname=self.__name__
|
||||||
|
for d in self._c.columns(tname):
|
||||||
|
b=ColumnBrowser()
|
||||||
|
b._d=d
|
||||||
|
try: b.icon=field_icons[d['Type']]
|
||||||
|
except: pass
|
||||||
|
b.TABLE_NAME=tname
|
||||||
|
r.append(b)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def tpId(self): return self._d['TABLE_NAME']
|
||||||
|
def tpURL(self): return "Table/%s" % self._d['TABLE_NAME']
|
||||||
|
def Name(self): return self._d['TABLE_NAME']
|
||||||
|
def Type(self): return self._d['TABLE_TYPE']
|
||||||
|
|
||||||
|
manage_designInput=HTMLFile('designInput',globals())
|
||||||
|
def manage_buildInput(self, id, source, default, REQUEST=None):
|
||||||
|
"Create a database method for an input form"
|
||||||
|
args=[]
|
||||||
|
values=[]
|
||||||
|
names=[]
|
||||||
|
columns=self._columns
|
||||||
|
for i in range(len(source)):
|
||||||
|
s=source[i]
|
||||||
|
if s=='Null': continue
|
||||||
|
c=columns[i]
|
||||||
|
d=default[i]
|
||||||
|
t=c['Type']
|
||||||
|
n=c['Name']
|
||||||
|
names.append(n)
|
||||||
|
if s=='Argument':
|
||||||
|
values.append("<dtml-sqlvar %s type=%s>'" %
|
||||||
|
(n, vartype(t)))
|
||||||
|
a='%s%s' % (n, boboType(t))
|
||||||
|
if d: a="%s=%s" % (a,d)
|
||||||
|
args.append(a)
|
||||||
|
elif s=='Property':
|
||||||
|
values.append("<dtml-sqlvar %s type=%s>'" %
|
||||||
|
(n, vartype(t)))
|
||||||
|
else:
|
||||||
|
if isStringType(t):
|
||||||
|
if find(d,"\'") >= 0: d=join(split(d,"\'"),"''")
|
||||||
|
values.append("'%s'" % d)
|
||||||
|
elif d:
|
||||||
|
values.append(str(d))
|
||||||
|
else:
|
||||||
|
raise ValueError, (
|
||||||
|
'no default was given for <em>%s</em>' % n)
|
||||||
|
|
||||||
|
class ColumnBrowser(Browser):
|
||||||
|
icon='field'
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
return ('\t<input type=checkbox name="%s.%s">' %
|
||||||
|
(self.TABLE_NAME, self._d['Name']))
|
||||||
|
def tpId(self): return self._d['Name']
|
||||||
|
def tpURL(self): return "Column/%s" % self._d['Name']
|
||||||
|
def Description(self):
|
||||||
|
d=self._d
|
||||||
|
if d['Scale']:
|
||||||
|
return " %(Type)s(%(Precision)s,%(Scale)s) %(Nullable)s" % d
|
||||||
|
else:
|
||||||
|
return " %(Type)s(%(Precision)s) %(Nullable)s" % d
|
||||||
|
|
||||||
|
table_icons={
|
||||||
|
'TABLE': 'table',
|
||||||
|
'VIEW':'view',
|
||||||
|
'SYSTEM_TABLE': 'stable',
|
||||||
|
}
|
||||||
|
|
||||||
|
field_icons={
|
||||||
|
NUMBER.name: 'i',
|
||||||
|
STRING.name: 'text',
|
||||||
|
DATETIME.name: 'date',
|
||||||
|
INTEGER.name: 'int',
|
||||||
|
FLOAT.name: 'float',
|
||||||
|
BOOLEAN.name: 'bin',
|
||||||
|
ROWID.name: 'int'
|
||||||
|
}
|
29
ZopeProducts/ZPsycopgDA/__init__.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# ZPsycopgDA/__init__.py - ZPsycopgDA Zope product
|
||||||
|
#
|
||||||
|
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
||||||
|
#
|
||||||
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# psycopg2 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 Lesser General Public
|
||||||
|
# License for more details.
|
||||||
|
|
||||||
|
# Import modules needed by _psycopg to allow tools like py2exe to do
|
||||||
|
# their work without bothering about the module dependencies.
|
||||||
|
|
||||||
|
__doc__ = "ZPsycopg Database Adapter Registration."
|
||||||
|
__version__ = '2.4.6'
|
||||||
|
|
||||||
|
import DA
|
||||||
|
|
||||||
|
def initialize(context):
|
||||||
|
context.registerClass(
|
||||||
|
DA.Connection,
|
||||||
|
permission = 'Add Z Psycopg 2 Database Connections',
|
||||||
|
constructors = (DA.manage_addZPsycopgConnectionForm,
|
||||||
|
DA.manage_addZPsycopgConnection),
|
||||||
|
icon = 'icons/DBAdapterFolder_icon.gif')
|
209
ZopeProducts/ZPsycopgDA/db.py
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
# ZPsycopgDA/db.py - query execution
|
||||||
|
#
|
||||||
|
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
||||||
|
#
|
||||||
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# psycopg2 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 Lesser General Public
|
||||||
|
# License for more details.
|
||||||
|
|
||||||
|
# Import modules needed by _psycopg to allow tools like py2exe to do
|
||||||
|
# their work without bothering about the module dependencies.
|
||||||
|
|
||||||
|
from Shared.DC.ZRDB.TM import TM
|
||||||
|
from Shared.DC.ZRDB import dbi_db
|
||||||
|
|
||||||
|
from ZODB.POSException import ConflictError
|
||||||
|
|
||||||
|
import site
|
||||||
|
import pool
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE, TIME
|
||||||
|
from psycopg2.extensions import TransactionRollbackError, register_type
|
||||||
|
from psycopg2 import NUMBER, STRING, ROWID, DATETIME
|
||||||
|
|
||||||
|
|
||||||
|
# the DB object, managing all the real query work
|
||||||
|
|
||||||
|
class DB(TM, dbi_db.DB):
|
||||||
|
|
||||||
|
_p_oid = _p_changed = _registered = None
|
||||||
|
|
||||||
|
def __init__(self, dsn, tilevel, typecasts, enc='utf-8'):
|
||||||
|
self.dsn = dsn
|
||||||
|
self.tilevel = tilevel
|
||||||
|
self.typecasts = typecasts
|
||||||
|
if enc is None or enc == "":
|
||||||
|
self.encoding = "utf-8"
|
||||||
|
else:
|
||||||
|
self.encoding = enc
|
||||||
|
self.failures = 0
|
||||||
|
self.calls = 0
|
||||||
|
self.make_mappings()
|
||||||
|
|
||||||
|
def getconn(self, init=True):
|
||||||
|
# if init is False we are trying to get hold on an already existing
|
||||||
|
# connection, so we avoid to (re)initialize it risking errors.
|
||||||
|
conn = pool.getconn(self.dsn)
|
||||||
|
if init:
|
||||||
|
# use set_session where available as in these versions
|
||||||
|
# set_isolation_level generates an extra query.
|
||||||
|
if psycopg2.__version__ >= '2.4.2':
|
||||||
|
conn.set_session(isolation_level=int(self.tilevel))
|
||||||
|
else:
|
||||||
|
conn.set_isolation_level(int(self.tilevel))
|
||||||
|
conn.set_client_encoding(self.encoding)
|
||||||
|
for tc in self.typecasts:
|
||||||
|
register_type(tc, conn)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def putconn(self, close=False):
|
||||||
|
try:
|
||||||
|
conn = pool.getconn(self.dsn, False)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
pool.putconn(self.dsn, conn, close)
|
||||||
|
|
||||||
|
def getcursor(self):
|
||||||
|
conn = self.getconn(False)
|
||||||
|
return conn.cursor()
|
||||||
|
|
||||||
|
def _finish(self, *ignored):
|
||||||
|
try:
|
||||||
|
conn = self.getconn(False)
|
||||||
|
conn.commit()
|
||||||
|
self.putconn()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _abort(self, *ignored):
|
||||||
|
try:
|
||||||
|
conn = self.getconn(False)
|
||||||
|
conn.rollback()
|
||||||
|
self.putconn()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
# this will create a new pool for our DSN if not already existing,
|
||||||
|
# then get and immediately release a connection
|
||||||
|
self.getconn()
|
||||||
|
self.putconn()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
# FIXME: if this connection is closed we flush all the pool associated
|
||||||
|
# with the current DSN; does this makes sense?
|
||||||
|
pool.flushpool(self.dsn)
|
||||||
|
|
||||||
|
def sortKey(self):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def make_mappings(self):
|
||||||
|
"""Generate the mappings used later by self.convert_description()."""
|
||||||
|
self.type_mappings = {}
|
||||||
|
for t, s in [(INTEGER,'i'), (LONGINTEGER, 'i'), (NUMBER, 'n'),
|
||||||
|
(BOOLEAN,'n'), (ROWID, 'i'),
|
||||||
|
(DATETIME, 'd'), (DATE, 'd'), (TIME, 'd')]:
|
||||||
|
for v in t.values:
|
||||||
|
self.type_mappings[v] = (t, s)
|
||||||
|
|
||||||
|
def convert_description(self, desc, use_psycopg_types=False):
|
||||||
|
"""Convert DBAPI-2.0 description field to Zope format."""
|
||||||
|
items = []
|
||||||
|
for name, typ, width, ds, p, scale, null_ok in desc:
|
||||||
|
m = self.type_mappings.get(typ, (STRING, 's'))
|
||||||
|
items.append({
|
||||||
|
'name': name,
|
||||||
|
'type': use_psycopg_types and m[0] or m[1],
|
||||||
|
'width': width,
|
||||||
|
'precision': p,
|
||||||
|
'scale': scale,
|
||||||
|
'null': null_ok,
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
|
||||||
|
## tables and rows ##
|
||||||
|
|
||||||
|
def tables(self, rdb=0, _care=('TABLE', 'VIEW')):
|
||||||
|
self._register()
|
||||||
|
c = self.getcursor()
|
||||||
|
c.execute(
|
||||||
|
"SELECT t.tablename AS NAME, 'TABLE' AS TYPE "
|
||||||
|
" FROM pg_tables t WHERE tableowner <> 'postgres' "
|
||||||
|
"UNION SELECT v.viewname AS NAME, 'VIEW' AS TYPE "
|
||||||
|
" FROM pg_views v WHERE viewowner <> 'postgres' "
|
||||||
|
"UNION SELECT t.tablename AS NAME, 'SYSTEM_TABLE\' AS TYPE "
|
||||||
|
" FROM pg_tables t WHERE tableowner = 'postgres' "
|
||||||
|
"UNION SELECT v.viewname AS NAME, 'SYSTEM_TABLE' AS TYPE "
|
||||||
|
"FROM pg_views v WHERE viewowner = 'postgres'")
|
||||||
|
res = []
|
||||||
|
for name, typ in c.fetchall():
|
||||||
|
if typ in _care:
|
||||||
|
res.append({'TABLE_NAME': name, 'TABLE_TYPE': typ})
|
||||||
|
self.putconn()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def columns(self, table_name):
|
||||||
|
self._register()
|
||||||
|
c = self.getcursor()
|
||||||
|
try:
|
||||||
|
r = c.execute('SELECT * FROM "%s" WHERE 1=0' % table_name)
|
||||||
|
except:
|
||||||
|
return ()
|
||||||
|
self.putconn()
|
||||||
|
return self.convert_description(c.description, True)
|
||||||
|
|
||||||
|
## query execution ##
|
||||||
|
|
||||||
|
def query(self, query_string, max_rows=None, query_data=None):
|
||||||
|
self._register()
|
||||||
|
self.calls = self.calls+1
|
||||||
|
|
||||||
|
desc = ()
|
||||||
|
res = []
|
||||||
|
nselects = 0
|
||||||
|
|
||||||
|
c = self.getcursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
for qs in [x for x in query_string.split('\0') if x]:
|
||||||
|
try:
|
||||||
|
if query_data:
|
||||||
|
c.execute(qs, query_data)
|
||||||
|
else:
|
||||||
|
c.execute(qs)
|
||||||
|
except TransactionRollbackError:
|
||||||
|
# Ha, here we have to look like we are the ZODB raising conflict errrors, raising ZPublisher.Publish.Retry just doesn't work
|
||||||
|
#logging.debug("Serialization Error, retrying transaction", exc_info=True)
|
||||||
|
raise ConflictError("TransactionRollbackError from psycopg2")
|
||||||
|
except psycopg2.OperationalError:
|
||||||
|
#logging.exception("Operational error on connection, closing it.")
|
||||||
|
try:
|
||||||
|
# Only close our connection
|
||||||
|
self.putconn(True)
|
||||||
|
except:
|
||||||
|
#logging.debug("Something went wrong when we tried to close the pool", exc_info=True)
|
||||||
|
pass
|
||||||
|
if c.description is not None:
|
||||||
|
nselects += 1
|
||||||
|
if c.description != desc and nselects > 1:
|
||||||
|
raise psycopg2.ProgrammingError(
|
||||||
|
'multiple selects in single query not allowed')
|
||||||
|
if max_rows:
|
||||||
|
res = c.fetchmany(max_rows)
|
||||||
|
else:
|
||||||
|
res = c.fetchall()
|
||||||
|
desc = c.description
|
||||||
|
self.failures = 0
|
||||||
|
|
||||||
|
except StandardError, err:
|
||||||
|
self._abort()
|
||||||
|
raise err
|
||||||
|
|
||||||
|
return self.convert_description(desc), res
|
108
ZopeProducts/ZPsycopgDA/dtml/add.dtml
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<dtml-var manage_page_header>
|
||||||
|
|
||||||
|
<dtml-var "manage_form_title(this(), _,
|
||||||
|
form_title='Add Z Psycopg 2 Database Connection',
|
||||||
|
help_product='ZPsycopgDA',
|
||||||
|
help_topic='ZPsycopgDA-Method-Add.stx'
|
||||||
|
)">
|
||||||
|
|
||||||
|
<p class="form-help">
|
||||||
|
A Zope Psycopg 2 Database Connection is used to connect and execute
|
||||||
|
queries on a PostgreSQL database.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="form-help">
|
||||||
|
In the form below <em>Connection String</em> (also called the Data Source Name
|
||||||
|
or DSN for short) is a string... (TODO: finish docs)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form action="manage_addZPsycopgConnection" method="POST">
|
||||||
|
<table cellspacing="0" cellpadding="2" border="0">
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Id
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="text" name="id" size="40"
|
||||||
|
value="Psycopg2_database_connection" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-optional">
|
||||||
|
Title
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="text" name="title" size="40"
|
||||||
|
value="Z Psycopg 2 Database Connection"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Connection string
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="text" name="connection_string" size="40" value="" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Connect immediately
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="checkbox" name="check" value="YES" checked="YES" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Use Zope's internal DateTime
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="checkbox" name="zdatetime" value="YES" checked="YES" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Transaction isolation level
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<select name="tilevel:int">
|
||||||
|
<option value="4">Read uncommitted</option>
|
||||||
|
<option value="1">Read committed</option>
|
||||||
|
<option value="2" selected="YES">Repeatable read</option>
|
||||||
|
<option value="3">Serializable</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Encoding
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="text" name="encoding" size="40" value="" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top" colspan="2">
|
||||||
|
<div class="form-element">
|
||||||
|
<input class="form-element" type="submit" name="submit" value=" Add " />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<dtml-var manage_page_footer>
|
11
ZopeProducts/ZPsycopgDA/dtml/browse.dtml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<html>
|
||||||
|
<head><title><dtml-var title_or_id >tables</title></head>
|
||||||
|
<body bgcolor="#FFFFFF" link="#000099" vlink="#555555" alink="#77003B">
|
||||||
|
<dtml-var manage_tabs>
|
||||||
|
<dtml-tree header="info">
|
||||||
|
<IMG SRC="<dtml-var SCRIPT_NAME >/misc_/ZPsycopgDA/<dtml-var icon>"
|
||||||
|
ALT="<dtml-var Type>" BORDER="0">
|
||||||
|
<dtml-var Name><dtml-var Description>
|
||||||
|
</dtml-tree>
|
||||||
|
</body>
|
||||||
|
</html>
|
84
ZopeProducts/ZPsycopgDA/dtml/edit.dtml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<dtml-var manage_page_header>
|
||||||
|
<dtml-var manage_tabs>
|
||||||
|
|
||||||
|
<form action="manage_edit" method="POST">
|
||||||
|
<table cellspacing="0" cellpadding="2" border="0">
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-optional">
|
||||||
|
Title
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="text" name="title" size="40"
|
||||||
|
value="&dtml-title;"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Connection string
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="text" name="connection_string" size="40"
|
||||||
|
value="&dtml-connection_string;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Use Zope's internal DateTime
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="checkbox" name="zdatetime" value="YES"
|
||||||
|
<dtml-if expr="zdatetime">checked="YES"</dtml-if> />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Transaction isolation level
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<select name="tilevel:int">
|
||||||
|
<option value="4"
|
||||||
|
<dtml-if expr="tilevel==4">selected="YES"</dtml-if>>
|
||||||
|
Read uncommitted</option>
|
||||||
|
<option value="1"
|
||||||
|
<dtml-if expr="tilevel==1">selected="YES"</dtml-if>>
|
||||||
|
Read committed</option>
|
||||||
|
<option value="2"
|
||||||
|
<dtml-if expr="tilevel==2">selected="YES"</dtml-if>>
|
||||||
|
Repeatable read</option>
|
||||||
|
<option value="3"
|
||||||
|
<dtml-if expr="tilevel==3">selected="YES"</dtml-if>>
|
||||||
|
Serializable</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<div class="form-label">
|
||||||
|
Encoding
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<input type="text" name="encoding" size="40"
|
||||||
|
value="&dtml-encoding;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top" colspan="2">
|
||||||
|
<div class="form-element">
|
||||||
|
<input class="form-element" type="submit" name="submit"
|
||||||
|
value=" Save Changes " />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<dtml-var manage_page_footer>
|
7
ZopeProducts/ZPsycopgDA/dtml/table_info.dtml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<dtml-var standard_html_header>
|
||||||
|
|
||||||
|
<dtml-var TABLE_TYPE><dtml-if TABLE_OWNER>
|
||||||
|
owned by <dtml-var TABLE_OWNER></dtml-if>
|
||||||
|
<dtml-if REMARKS><br><dtml-var REMARKS></dtml-if>
|
||||||
|
|
||||||
|
<dtml-var standard_html_footer>
|
BIN
app/static/icons/favicon.ico → ZopeProducts/ZPsycopgDA/icons/DBAdapterFolder_icon.gif
Normal file → Executable file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 897 B |
BIN
ZopeProducts/ZPsycopgDA/icons/bin.gif
Normal file
After Width: | Height: | Size: 924 B |
BIN
ZopeProducts/ZPsycopgDA/icons/date.gif
Normal file
After Width: | Height: | Size: 930 B |
BIN
ZopeProducts/ZPsycopgDA/icons/datetime.gif
Normal file
After Width: | Height: | Size: 925 B |
BIN
ZopeProducts/ZPsycopgDA/icons/field.gif
Normal file
After Width: | Height: | Size: 915 B |
BIN
ZopeProducts/ZPsycopgDA/icons/float.gif
Normal file
After Width: | Height: | Size: 929 B |
BIN
ZopeProducts/ZPsycopgDA/icons/int.gif
Normal file
After Width: | Height: | Size: 918 B |
BIN
ZopeProducts/ZPsycopgDA/icons/stable.gif
Normal file
After Width: | Height: | Size: 884 B |
BIN
ZopeProducts/ZPsycopgDA/icons/table.gif
Normal file
After Width: | Height: | Size: 878 B |
BIN
ZopeProducts/ZPsycopgDA/icons/text.gif
Normal file
After Width: | Height: | Size: 918 B |
BIN
ZopeProducts/ZPsycopgDA/icons/time.gif
Normal file
After Width: | Height: | Size: 926 B |
BIN
ZopeProducts/ZPsycopgDA/icons/view.gif
Normal file
After Width: | Height: | Size: 893 B |
BIN
ZopeProducts/ZPsycopgDA/icons/what.gif
Normal file
After Width: | Height: | Size: 894 B |
193
ZopeProducts/ZPsycopgDA/pool.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
# ZPsycopgDA/pool.py - ZPsycopgDA Zope product: connection pooling
|
||||||
|
#
|
||||||
|
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
|
||||||
|
#
|
||||||
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# psycopg2 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 Lesser General Public
|
||||||
|
# License for more details.
|
||||||
|
|
||||||
|
# Import modules needed by _psycopg to allow tools like py2exe to do
|
||||||
|
# their work without bothering about the module dependencies.
|
||||||
|
|
||||||
|
# All the connections are held in a pool of pools, directly accessible by the
|
||||||
|
# ZPsycopgDA code in db.py.
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import psycopg2
|
||||||
|
from psycopg2.pool import PoolError
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractConnectionPool(object):
|
||||||
|
"""Generic key-based pooling code."""
|
||||||
|
|
||||||
|
def __init__(self, minconn, maxconn, *args, **kwargs):
|
||||||
|
"""Initialize the connection pool.
|
||||||
|
|
||||||
|
New 'minconn' connections are created immediately calling 'connfunc'
|
||||||
|
with given parameters. The connection pool will support a maximum of
|
||||||
|
about 'maxconn' connections.
|
||||||
|
"""
|
||||||
|
self.minconn = minconn
|
||||||
|
self.maxconn = maxconn
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
self._args = args
|
||||||
|
self._kwargs = kwargs
|
||||||
|
|
||||||
|
self._pool = []
|
||||||
|
self._used = {}
|
||||||
|
self._rused = {} # id(conn) -> key map
|
||||||
|
self._keys = 0
|
||||||
|
|
||||||
|
for i in range(self.minconn):
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
def _connect(self, key=None):
|
||||||
|
"""Create a new connection and assign it to 'key' if not None."""
|
||||||
|
conn = psycopg2.connect(*self._args, **self._kwargs)
|
||||||
|
if key is not None:
|
||||||
|
self._used[key] = conn
|
||||||
|
self._rused[id(conn)] = key
|
||||||
|
else:
|
||||||
|
self._pool.append(conn)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def _getkey(self):
|
||||||
|
"""Return a new unique key."""
|
||||||
|
self._keys += 1
|
||||||
|
return self._keys
|
||||||
|
|
||||||
|
def _getconn(self, key=None):
|
||||||
|
"""Get a free connection and assign it to 'key' if not None."""
|
||||||
|
if self.closed: raise PoolError("connection pool is closed")
|
||||||
|
if key is None: key = self._getkey()
|
||||||
|
|
||||||
|
if key in self._used:
|
||||||
|
return self._used[key]
|
||||||
|
|
||||||
|
if self._pool:
|
||||||
|
self._used[key] = conn = self._pool.pop()
|
||||||
|
self._rused[id(conn)] = key
|
||||||
|
return conn
|
||||||
|
else:
|
||||||
|
if len(self._used) == self.maxconn:
|
||||||
|
raise PoolError("connection pool exausted")
|
||||||
|
return self._connect(key)
|
||||||
|
|
||||||
|
def _putconn(self, conn, key=None, close=False):
|
||||||
|
"""Put away a connection."""
|
||||||
|
if self.closed: raise PoolError("connection pool is closed")
|
||||||
|
if key is None: key = self._rused[id(conn)]
|
||||||
|
|
||||||
|
if not key:
|
||||||
|
raise PoolError("trying to put unkeyed connection")
|
||||||
|
|
||||||
|
if len(self._pool) < self.minconn and not close:
|
||||||
|
self._pool.append(conn)
|
||||||
|
else:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# here we check for the presence of key because it can happen that a
|
||||||
|
# thread tries to put back a connection after a call to close
|
||||||
|
if not self.closed or key in self._used:
|
||||||
|
del self._used[key]
|
||||||
|
del self._rused[id(conn)]
|
||||||
|
|
||||||
|
def _closeall(self):
|
||||||
|
"""Close all connections.
|
||||||
|
|
||||||
|
Note that this can lead to some code fail badly when trying to use
|
||||||
|
an already closed connection. If you call .closeall() make sure
|
||||||
|
your code can deal with it.
|
||||||
|
"""
|
||||||
|
if self.closed: raise PoolError("connection pool is closed")
|
||||||
|
for conn in self._pool + list(self._used.values()):
|
||||||
|
try:
|
||||||
|
conn.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
|
||||||
|
class PersistentConnectionPool(AbstractConnectionPool):
|
||||||
|
"""A pool that assigns persistent connections to different threads.
|
||||||
|
|
||||||
|
Note that this connection pool generates by itself the required keys
|
||||||
|
using the current thread id. This means that until a thread puts away
|
||||||
|
a connection it will always get the same connection object by successive
|
||||||
|
`!getconn()` calls. This also means that a thread can't use more than one
|
||||||
|
single connection from the pool.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, minconn, maxconn, *args, **kwargs):
|
||||||
|
"""Initialize the threading lock."""
|
||||||
|
import threading
|
||||||
|
AbstractConnectionPool.__init__(
|
||||||
|
self, minconn, maxconn, *args, **kwargs)
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
# we we'll need the thread module, to determine thread ids, so we
|
||||||
|
# import it here and copy it in an instance variable
|
||||||
|
import thread
|
||||||
|
self.__thread = thread
|
||||||
|
|
||||||
|
def getconn(self):
|
||||||
|
"""Generate thread id and return a connection."""
|
||||||
|
key = self.__thread.get_ident()
|
||||||
|
self._lock.acquire()
|
||||||
|
try:
|
||||||
|
return self._getconn(key)
|
||||||
|
finally:
|
||||||
|
self._lock.release()
|
||||||
|
|
||||||
|
def putconn(self, conn=None, close=False):
|
||||||
|
"""Put away an unused connection."""
|
||||||
|
key = self.__thread.get_ident()
|
||||||
|
self._lock.acquire()
|
||||||
|
try:
|
||||||
|
if not conn: conn = self._used[key]
|
||||||
|
self._putconn(conn, key, close)
|
||||||
|
finally:
|
||||||
|
self._lock.release()
|
||||||
|
|
||||||
|
def closeall(self):
|
||||||
|
"""Close all connections (even the one currently in use.)"""
|
||||||
|
self._lock.acquire()
|
||||||
|
try:
|
||||||
|
self._closeall()
|
||||||
|
finally:
|
||||||
|
self._lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
_connections_pool = {}
|
||||||
|
_connections_lock = threading.Lock()
|
||||||
|
|
||||||
|
def getpool(dsn, create=True):
|
||||||
|
_connections_lock.acquire()
|
||||||
|
try:
|
||||||
|
if not _connections_pool.has_key(dsn) and create:
|
||||||
|
_connections_pool[dsn] = \
|
||||||
|
PersistentConnectionPool(4, 200, dsn)
|
||||||
|
finally:
|
||||||
|
_connections_lock.release()
|
||||||
|
return _connections_pool[dsn]
|
||||||
|
|
||||||
|
def flushpool(dsn):
|
||||||
|
_connections_lock.acquire()
|
||||||
|
try:
|
||||||
|
_connections_pool[dsn].closeall()
|
||||||
|
del _connections_pool[dsn]
|
||||||
|
finally:
|
||||||
|
_connections_lock.release()
|
||||||
|
|
||||||
|
def getconn(dsn, create=True):
|
||||||
|
return getpool(dsn, create=create).getconn()
|
||||||
|
|
||||||
|
def putconn(dsn, conn, close=False):
|
||||||
|
getpool(dsn).putconn(conn, close=close)
|
47
ZopeProducts/exUserFolder/AuthSources/__init__.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:34 akm Exp $
|
||||||
|
|
||||||
|
#import etcAuthSource
|
||||||
|
#import httpsAuthSource
|
||||||
|
#import mysqlAuthSource
|
||||||
|
import pgAuthSource
|
||||||
|
#import pgAuthSourceAlt
|
||||||
|
#import radiusAuthSource
|
||||||
|
#import smbAuthSource
|
||||||
|
#import usAuthSource
|
||||||
|
#import zodbAuthSource
|
||||||
|
#import zodbBTreeAuthSource
|
||||||
|
|
||||||
|
#
|
||||||
|
# These have special requirements for external libraries
|
||||||
|
# that my not be present.
|
||||||
|
#
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# import nisAuthSource
|
||||||
|
# except:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# import LDAPAuthSource
|
||||||
|
# except:
|
||||||
|
# pass
|
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
@ -0,0 +1,2 @@
|
|||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:36 akm Exp $
|
||||||
|
import pgAuthSource
|
@ -0,0 +1,40 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None,_,DialogTitle='Add Postgresql Authentication Source')">
|
||||||
|
<FORM ACTION="&dtml-URL;" METHOD="POST">
|
||||||
|
<dtml-in "REQUEST.form.keys()">
|
||||||
|
<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
|
||||||
|
</dtml-in>
|
||||||
|
<input type="HIDDEN" name="doProp" value="1">
|
||||||
|
<TABLE CELLSPACING="2">
|
||||||
|
<tr><th><dtml-babel src="'en'">Database Connection</dtml-babel>:</th>
|
||||||
|
<td>
|
||||||
|
<select name="pgauth_connection">
|
||||||
|
<dtml-in "SQLConnectionIDs()">
|
||||||
|
<option value="<dtml-var sequence-item>">
|
||||||
|
<dtml-var sequence-key></option>
|
||||||
|
</dtml-in>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><dtml-babel src="'en'">Table Name</dtml-babel>:</th>
|
||||||
|
<td><input type="text" name="pgauth_table" value="passwd"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><dtml-babel src="'en'">Username Column</dtml-babel>:</th>
|
||||||
|
<td><input type="text" name="pgauth_usernameColumn" value="username"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><dtml-babel src="'en'">Password Column</dtml-babel>:</th>
|
||||||
|
<td><input type="text" name="pgauth_passwordColumn" value="password"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><dtml-babel src="'en'">Roles Column</dtml-babel>:</th>
|
||||||
|
<td><input type="text" name="pgauth_rolesColumn" value="roles"></td>
|
||||||
|
</tr>
|
||||||
|
<TR>
|
||||||
|
<TD></TD>
|
||||||
|
<TD><BR><INPUT TYPE="SUBMIT" VALUE="<dtml-babel src="'en'">Add</dtml-babel>"></TD>
|
||||||
|
</TR>
|
||||||
|
</TABLE>
|
||||||
|
</FORM>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,37 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None,_,DialogTitle='Postgresql Authentication Source',dialog_width='100%')">
|
||||||
|
<dtml-var manage_tabs>
|
||||||
|
<FORM ACTION="manage_editAuthSource" METHOD="POST">
|
||||||
|
<TABLE CELLSPACING="2">
|
||||||
|
<tr><th><dtml-babel src="'en'">Database Connection</dtml-babel>:</th>
|
||||||
|
<td>
|
||||||
|
<select name="pgauth_connection">
|
||||||
|
<dtml-in "SQLConnectionIDs()">
|
||||||
|
<option value="<dtml-var sequence-item>"<dtml-if "currentAuthSource.connection==_['sequence-item']"> SELECTED</dtml-if>>
|
||||||
|
<dtml-var sequence-key></option>
|
||||||
|
</dtml-in>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><dtml-babel src="'en'">Table Name</dtml-babel>:</th>
|
||||||
|
<td><input type="text" name="pgauth_table" value="<dtml-var "currentAuthSource.table">"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><dtml-babel src="'en'">Username Column</dtml-babel>:</th>
|
||||||
|
<td><input type="text" name="pgauth_usernameColumn" value="<dtml-var "currentAuthSource.usernameColumn">"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><dtml-babel src="'en'">Password Column</dtml-babel>:</th>
|
||||||
|
<td><input type="text" name="pgauth_passwordColumn" value="<dtml-var "currentAuthSource.passwordColumn">"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><dtml-babel src="'en'">Roles Column</dtml-babel>:</th>
|
||||||
|
<td><input type="text" name="pgauth_rolesColumn" value="<dtml-var "currentAuthSource.rolesColumn">"></td>
|
||||||
|
</tr>
|
||||||
|
<TR>
|
||||||
|
<TD></TD>
|
||||||
|
<TD><BR><INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">Edit</dtml-babel> "></TD>
|
||||||
|
</TR>
|
||||||
|
</TABLE>
|
||||||
|
</FORM>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,333 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# Postgres Authentication Source for exUserFolder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: pgAuthSource.py,v 1.1 2004/11/10 14:15:36 akm Exp $
|
||||||
|
|
||||||
|
#
|
||||||
|
# This class only authenticates users, it stores no properties.
|
||||||
|
#
|
||||||
|
|
||||||
|
import string,Acquisition
|
||||||
|
|
||||||
|
from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
|
||||||
|
|
||||||
|
from OFS.Folder import Folder
|
||||||
|
|
||||||
|
from Products.ZSQLMethods.SQL import SQL
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import PluginRegister
|
||||||
|
|
||||||
|
try:
|
||||||
|
from crypt import crypt
|
||||||
|
except:
|
||||||
|
from Products.exUserFolder.fcrypt.fcrypt import crypt
|
||||||
|
|
||||||
|
# debug XXX
|
||||||
|
# def xLOG(msg):
|
||||||
|
# f = open('/tmp/debug.log','a')
|
||||||
|
# f.write(msg+'\n')
|
||||||
|
# f.close()
|
||||||
|
|
||||||
|
def manage_addpgAuthSource(self, REQUEST):
|
||||||
|
""" Add a Postgres Auth Source """
|
||||||
|
|
||||||
|
connection=REQUEST['pgauth_connection']
|
||||||
|
table=REQUEST['pgauth_table']
|
||||||
|
usernameColumn=REQUEST['pgauth_usernameColumn']
|
||||||
|
passwordColumn=REQUEST['pgauth_passwordColumn']
|
||||||
|
rolesColumn=REQUEST['pgauth_rolesColumn']
|
||||||
|
o = pgAuthSource(connection, table, usernameColumn, passwordColumn,
|
||||||
|
rolesColumn)
|
||||||
|
self._setObject('pgAuthSource', o, None, None, 0)
|
||||||
|
o=getattr(self,'pgAuthSource')
|
||||||
|
if hasattr(o, 'postInitialisation'):
|
||||||
|
o.postInitialisation(REQUEST)
|
||||||
|
|
||||||
|
self.currentAuthSource=o
|
||||||
|
return ''
|
||||||
|
|
||||||
|
manage_addpgAuthSourceForm=HTMLFile('manage_addpgAuthSourceForm', globals())
|
||||||
|
manage_editpgAuthSourceForm=HTMLFile('manage_editpgAuthSourceForm', globals())
|
||||||
|
|
||||||
|
class pgAuthSource(Folder):
|
||||||
|
""" Authenticate Users against a Postgres Database """
|
||||||
|
|
||||||
|
meta_type='Authentication Source'
|
||||||
|
title='Postgresql Authentication'
|
||||||
|
|
||||||
|
icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
|
||||||
|
|
||||||
|
manage_tabs=Acquisition.Acquired
|
||||||
|
|
||||||
|
manage_editForm=manage_editpgAuthSourceForm
|
||||||
|
|
||||||
|
#
|
||||||
|
# You can define this to go off and do the authentication instead of
|
||||||
|
# using the basic one inside the User Object
|
||||||
|
#
|
||||||
|
remoteAuthMethod=None
|
||||||
|
|
||||||
|
def __init__(self, connection, table, usernameColumn, passwordColumn,
|
||||||
|
rolesColumn):
|
||||||
|
self.id='pgAuthSource'
|
||||||
|
self.connection=connection
|
||||||
|
self.table=table
|
||||||
|
self.usernameColumn=usernameColumn
|
||||||
|
self.passwordColumn=passwordColumn
|
||||||
|
self.rolesColumn=rolesColumn
|
||||||
|
self.addSQLQueries()
|
||||||
|
|
||||||
|
def manage_editAuthSource(self, REQUEST):
|
||||||
|
""" Edit a Postgres Auth Source """
|
||||||
|
|
||||||
|
self.connection=REQUEST['pgauth_connection']
|
||||||
|
self.table=REQUEST['pgauth_table']
|
||||||
|
self.usernameColumn=REQUEST['pgauth_usernameColumn']
|
||||||
|
self.passwordColumn=REQUEST['pgauth_passwordColumn']
|
||||||
|
self.rolesColumn=REQUEST['pgauth_rolesColumn']
|
||||||
|
self.delSQLQueries()
|
||||||
|
self.addSQLQueries() # Re-add queries with new parameters
|
||||||
|
|
||||||
|
def createUser(self, username, password, roles):
|
||||||
|
""" Add A Username """
|
||||||
|
|
||||||
|
if type(roles) != type([]):
|
||||||
|
if roles:
|
||||||
|
roles=list(roles)
|
||||||
|
else:
|
||||||
|
roles=[]
|
||||||
|
|
||||||
|
rolestring=''
|
||||||
|
for role in roles:
|
||||||
|
rolestring=rolestring+role+','
|
||||||
|
|
||||||
|
rolestring=rolestring[:-1]
|
||||||
|
secret=self.cryptPassword(username, password)
|
||||||
|
self.sqlInsertUser(username=username,
|
||||||
|
password=secret,
|
||||||
|
roles=rolestring)
|
||||||
|
self._v_lastUser={}
|
||||||
|
|
||||||
|
def updateUser(self, username, password, roles):
|
||||||
|
if type(roles) != type([]):
|
||||||
|
if roles:
|
||||||
|
roles=list(roles)
|
||||||
|
else:
|
||||||
|
roles=[]
|
||||||
|
|
||||||
|
rolestring=''
|
||||||
|
for role in roles:
|
||||||
|
print role
|
||||||
|
rolestring=rolestring+role+','
|
||||||
|
|
||||||
|
rolestring=rolestring[:-1]
|
||||||
|
|
||||||
|
# Don't change passwords if it's null
|
||||||
|
if password:
|
||||||
|
secret=self.cryptPassword(username, password)
|
||||||
|
self.sqlUpdateUserPassword(username=username,
|
||||||
|
password=secret)
|
||||||
|
|
||||||
|
self.sqlUpdateUser(username=username,
|
||||||
|
roles=rolestring)
|
||||||
|
self._v_lastUser={}
|
||||||
|
|
||||||
|
def delSQLQueries(self):
|
||||||
|
sqllist=self.objectIds('Z SQL Method')
|
||||||
|
self.manage_delObjects(ids=sqllist)
|
||||||
|
|
||||||
|
def addSQLQueries(self):
|
||||||
|
sqlListUsers=SQL(
|
||||||
|
'sqlListUsers',
|
||||||
|
'List All Users',
|
||||||
|
self.connection,
|
||||||
|
'table=%s'%(self.table),
|
||||||
|
_sqlListUsers)
|
||||||
|
|
||||||
|
self._setObject('sqlListUsers', sqlListUsers)
|
||||||
|
|
||||||
|
sqlListOneUser=SQL(
|
||||||
|
'sqlListOneUser',
|
||||||
|
'List ONE User',
|
||||||
|
self.connection,
|
||||||
|
'table=%s usernameColumn=%s username:string'%(
|
||||||
|
self.table, self.usernameColumn),
|
||||||
|
_sqlListOneUser)
|
||||||
|
|
||||||
|
self._setObject('sqlListOneUser', sqlListOneUser)
|
||||||
|
|
||||||
|
sqlDeleteOneUser=SQL(
|
||||||
|
'sqlDeleteOneUser',
|
||||||
|
'Delete One User',
|
||||||
|
self.connection,
|
||||||
|
'table=%s usernameColumn=%s username:string'%(
|
||||||
|
self.table,self.usernameColumn),
|
||||||
|
_sqlDeleteOneUser)
|
||||||
|
|
||||||
|
self._setObject('sqlDeleteOneUser', sqlDeleteOneUser)
|
||||||
|
|
||||||
|
sqlInsertUser=SQL(
|
||||||
|
'sqlInsertUser',
|
||||||
|
'Insert One User',
|
||||||
|
self.connection,
|
||||||
|
'table=%s usernameColumn=%s passwordColumn=%s rolesColumn=%s username:string password:string roles:string'%(
|
||||||
|
self.table, self.usernameColumn, self.passwordColumn, self.rolesColumn),
|
||||||
|
_sqlInsertUser)
|
||||||
|
|
||||||
|
self._setObject('sqlInsertUser', sqlInsertUser)
|
||||||
|
|
||||||
|
sqlUpdateUser=SQL(
|
||||||
|
'sqlUpdateUser',
|
||||||
|
'Update User',
|
||||||
|
self.connection,
|
||||||
|
'table=%s rolesColumn=%s username:string roles:string'%(self.table, self.rolesColumn),
|
||||||
|
_sqlUpdateUser)
|
||||||
|
|
||||||
|
self._setObject('sqlUpdateUser', sqlUpdateUser)
|
||||||
|
|
||||||
|
sqlUpdateUserPassword=SQL(
|
||||||
|
'sqlUpdateUserPassword',
|
||||||
|
'Update just the password',
|
||||||
|
self.connection,
|
||||||
|
'table=%s usernameColumn=%s passwordColumn=%s username:string password:string'%(self.table, self.usernameColumn, self.passwordColumn),
|
||||||
|
_sqlUpdateUserPassword)
|
||||||
|
|
||||||
|
self._setObject('sqlUpdateUserPassword', sqlUpdateUserPassword)
|
||||||
|
|
||||||
|
def cryptPassword_old(self, username, password):
|
||||||
|
salt =username[:2]
|
||||||
|
secret = crypt(password, salt)
|
||||||
|
return secret
|
||||||
|
|
||||||
|
def deleteUsers(self, userids):
|
||||||
|
for uid in userids:
|
||||||
|
self.sqlDeleteOneUser(username=uid)
|
||||||
|
self._v_lastUser={}
|
||||||
|
|
||||||
|
def listUserNames(self):
|
||||||
|
"""Returns a real list of user names """
|
||||||
|
users = []
|
||||||
|
result=self.sqlListUsers()
|
||||||
|
for n in result:
|
||||||
|
username=sqlattr(n,self.usernameColumn)
|
||||||
|
users.append(username)
|
||||||
|
return users
|
||||||
|
|
||||||
|
def listUsers(self):
|
||||||
|
"""Returns a list of user names or [] if no users exist"""
|
||||||
|
users = []
|
||||||
|
result=self.sqlListUsers()
|
||||||
|
for n in result:
|
||||||
|
roles=[]
|
||||||
|
username=sqlattr(n,self.usernameColumn)
|
||||||
|
if sqlattr(n, self.rolesColumn):
|
||||||
|
roles=string.split(sqlattr(n,self.rolesColumn),',')
|
||||||
|
password=sqlattr(n, self.passwordColumn)
|
||||||
|
N={'username':username, 'password':password, 'roles':roles}
|
||||||
|
users.append(N)
|
||||||
|
return users
|
||||||
|
|
||||||
|
def listOneUser(self,username):
|
||||||
|
#xLOG('pg.listOneUser(%s)' % username)
|
||||||
|
if getattr(self, '_v_lastUser', {}):
|
||||||
|
if self._v_lastUser['username']==username:
|
||||||
|
return self._v_lastUser['users']
|
||||||
|
#xLOG('pg.listOneUser continuing')
|
||||||
|
users = []
|
||||||
|
result=self.sqlListOneUser(username=username)
|
||||||
|
#xLOG('pg.listOneUser result=%s' % result)
|
||||||
|
for n in result:
|
||||||
|
roles=[]
|
||||||
|
username=sqlattr(n,self.usernameColumn)
|
||||||
|
password=sqlattr(n,self.passwordColumn)
|
||||||
|
if sqlattr(n, self.rolesColumn):
|
||||||
|
roles=string.split(sqlattr(n,self.rolesColumn),',') #Andreas
|
||||||
|
N={'username':username, 'password':password, 'roles':roles}
|
||||||
|
users.append(N)
|
||||||
|
self._v_lastUser={}
|
||||||
|
self._v_lastUser['username']=username
|
||||||
|
self._v_lastUser['users']=users
|
||||||
|
return users
|
||||||
|
|
||||||
|
def postInitialisation(self, REQUEST):
|
||||||
|
self._v_lastUser={}
|
||||||
|
|
||||||
|
pgAuthReg=PluginRegister('pgAuthSource', 'Postgresql Authentication Source',
|
||||||
|
pgAuthSource, manage_addpgAuthSourceForm,
|
||||||
|
manage_addpgAuthSource,
|
||||||
|
manage_editpgAuthSourceForm)
|
||||||
|
exUserFolder.authSources['pgAuthSource']=pgAuthReg
|
||||||
|
|
||||||
|
from string import upper, lower
|
||||||
|
import Missing
|
||||||
|
mt=type(Missing.Value)
|
||||||
|
|
||||||
|
def typeconv(val):
|
||||||
|
if type(val)==mt:
|
||||||
|
return ''
|
||||||
|
return val
|
||||||
|
|
||||||
|
def sqlattr(ob, attr):
|
||||||
|
name=attr
|
||||||
|
if hasattr(ob, attr):
|
||||||
|
return typeconv(getattr(ob, attr))
|
||||||
|
attr=upper(attr)
|
||||||
|
if hasattr(ob, attr):
|
||||||
|
return typeconv(getattr(ob, attr))
|
||||||
|
attr=lower(attr)
|
||||||
|
if hasattr(ob, attr):
|
||||||
|
return typeconv(getattr(ob, attr))
|
||||||
|
raise NameError, name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_sqlListUsers="""
|
||||||
|
SELECT * FROM <dtml-var table>
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlListOneUser="""
|
||||||
|
SELECT * FROM <dtml-var table>
|
||||||
|
where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlDeleteOneUser="""
|
||||||
|
DELETE FROM <dtml-var table>
|
||||||
|
where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlInsertUser="""
|
||||||
|
INSERT INTO <dtml-var table> (<dtml-var usernameColumn>, <dtml-var passwordColumn>, <dtml-var rolesColumn>)
|
||||||
|
VALUES (<dtml-sqlvar username type=string>,
|
||||||
|
<dtml-sqlvar password type=string>,
|
||||||
|
<dtml-sqlvar roles type=string>)
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlUpdateUserPassword="""
|
||||||
|
UPDATE <dtml-var table> set <dtml-var passwordColumn>=<dtml-sqlvar password type=string>
|
||||||
|
WHERE <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlUpdateUser="""
|
||||||
|
UPDATE <dtml-var table> set <dtml-var rolesColumn>=<dtml-sqlvar roles type=string>
|
||||||
|
WHERE <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
|
||||||
|
"""
|
865
ZopeProducts/exUserFolder/CHANGES.txt
Normal file
@ -0,0 +1,865 @@
|
|||||||
|
Changes for 0.50.1
|
||||||
|
|
||||||
|
Add a README.Upgrading file to explain the impact of the 0.50.0 source
|
||||||
|
restructure, since people don't seem to be reading this file. --akm
|
||||||
|
|
||||||
|
Fix the default docLogin to use &dtml-URL as the default destination.
|
||||||
|
I porked the fcrypt import. It obviously doesn't get imported here since
|
||||||
|
I have a crypt module installed. -- akm
|
||||||
|
|
||||||
|
Fixed; https://sourceforge.net/tracker/?func=detail&aid=1084903&group_id=36318&atid=416446
|
||||||
|
thanks to vigine -- akm
|
||||||
|
|
||||||
|
Changes for 0.50.0
|
||||||
|
|
||||||
|
Restructured Source Tree. This will make this version incompatible with
|
||||||
|
previous versions, as the classes have moved. This breaks upgrading existing
|
||||||
|
installs unless you keep the old classes around. If you only use external
|
||||||
|
Auth/Prop/Group sources, you will probably be unaffected.
|
||||||
|
|
||||||
|
o Auth Sources moved to single directory
|
||||||
|
o Prop Sources moved to single directory
|
||||||
|
o Group Sources moved to single directory
|
||||||
|
o Docs moved to doc directory
|
||||||
|
--akm
|
||||||
|
|
||||||
|
Added Pluggable Crypto methods. Any authSource that contains a
|
||||||
|
cryptPassword method, will have it's method called, otherwise the
|
||||||
|
method selected by the user is called. --akm
|
||||||
|
|
||||||
|
Removed the cryptPassword method from existing Auth Sources. --akm
|
||||||
|
|
||||||
|
docLoginRedirect is no longer used. --akm
|
||||||
|
|
||||||
|
Changes for 0.20.2
|
||||||
|
BLAH! I missed some LDAP changes! --akm
|
||||||
|
|
||||||
|
Changes for 0.20.1
|
||||||
|
|
||||||
|
Fix import problem for pgPropSource --akm
|
||||||
|
Add performance boost to pgAuthSource and pgPropSource --akm
|
||||||
|
Make zodbAuthSource.listUsernames return a list. --akm
|
||||||
|
Update some LDAP Auth source bugs. --akm
|
||||||
|
Change references to "Authorisation" to "Authentication" since XUF
|
||||||
|
auth sources authenticate, they don't authorise. --akm
|
||||||
|
Changed the <h3> tags to <b> tags in the manage_adds.
|
||||||
|
|
||||||
|
Changes for 0.20.0
|
||||||
|
|
||||||
|
Fix:
|
||||||
|
https://sourceforge.net/tracker/index.php?func=detail&aid=547327&group_id=36318&atid=416446
|
||||||
|
https://sourceforge.net/tracker/index.php?func=detail&aid=616485&group_id=36318&atid=416448
|
||||||
|
https://sourceforge.net/tracker/index.php?func=detail&aid=594081&group_id=36318&atid=416448
|
||||||
|
https://sourceforge.net/tracker/index.php?func=detail&aid=594526&group_id=36318&atid=416448
|
||||||
|
|
||||||
|
Added LDAPAuthSource, based on the auth_ldap module for Apache
|
||||||
|
(http://www.rudedog.org/auth_ldap/) and the NDS Auth Source of
|
||||||
|
Phil Harris (AKA ftmpsh). This is only lightly tested, I don't have
|
||||||
|
the LDAP resources here to test all the features. Binding using uid/
|
||||||
|
cn and using various filters works (if the userPassword item is
|
||||||
|
present). This needs more testing by people with better LDAP setups
|
||||||
|
that I do. --akm
|
||||||
|
|
||||||
|
Padded docLoginRedirect to prevent IE from displaying "Friendly" error
|
||||||
|
messages when -D flag not present when running Zope --akm.
|
||||||
|
|
||||||
|
Update UZG to contain entry for LDAPAuthSource. Reformat text
|
||||||
|
slightly. --akm
|
||||||
|
|
||||||
|
Propogate "unable to auth" here requests up. This means the Manager
|
||||||
|
doesn't get locked out in cookie mode after adding an XUF instance.
|
||||||
|
It also means that people using a non-existant username at this level
|
||||||
|
get thrown up a level higher. This might not be what people want to
|
||||||
|
happen. --akm
|
||||||
|
|
||||||
|
Added method makeRedirectPath which is called from docLoginRedirect.
|
||||||
|
This makes the destination include any querystring that was present
|
||||||
|
when needing to redirect. -- akm.
|
||||||
|
|
||||||
|
Removed some Class globals from exUseFolder.py. These are now set
|
||||||
|
in __set_state__ if not present in the class so that upgrading users
|
||||||
|
don't get a crash (hopefully). -- akm.
|
||||||
|
|
||||||
|
pgPropSource was losing track of properties under heavy load.
|
||||||
|
Only noticable if you were setting and deleting a lot of temporary
|
||||||
|
properties. There is a global property timeout for pgPropSource. --akm
|
||||||
|
|
||||||
|
Jason Gibson <jason.gibson@sbcglobal.net> provided a nisAuthSource,
|
||||||
|
I've added it here --akm.
|
||||||
|
|
||||||
|
Refactored validate method to behave a lot more like BasicUserFolder.
|
||||||
|
Among other things, this fixes the issue where a local role could not
|
||||||
|
be granted to a user and granted permissions on the same object. --mb
|
||||||
|
|
||||||
|
Add NuxUserGroups support (previously on NuxUserGroups_support_branch)
|
||||||
|
and group sources. --bmh, mb
|
||||||
|
|
||||||
|
Now passes authFailedCode to Membership Login Page, The Default Login
|
||||||
|
Page as defined in the README.Membership will correctly display reason
|
||||||
|
for login being required --cab
|
||||||
|
|
||||||
|
Fixed Edit management pages for user-supplied auth and property
|
||||||
|
sources --bmh
|
||||||
|
|
||||||
|
Removed overriding of __len__ to return the number of users. This was
|
||||||
|
causing performance problems during authentication. See
|
||||||
|
http://sourceforge.net/mailarchive/message.php?msg_id=2230743 for
|
||||||
|
details. WARNING: this means using len(acl_users) to get the number
|
||||||
|
of users will no longer work! If you were using this trick, please
|
||||||
|
use len(acl_users.listUsers()) instead. --bmh
|
||||||
|
|
||||||
|
Make title property editable --bmh
|
||||||
|
|
||||||
|
Make Group Sources changeable dynamically after the acl_users folder has
|
||||||
|
been created --bmh
|
||||||
|
|
||||||
|
Inital import of https Auth source. Also, added a listUsers method
|
||||||
|
to the zodbBTreeProps source to support listUsers. -- jsb <jonah at cloud9.net>
|
||||||
|
|
||||||
|
Changes for 0.10.10
|
||||||
|
|
||||||
|
Added mysql Auth and mysql Prop source and mysql.sql schema. Just a
|
||||||
|
copy of the appropriate pg source with sql that works with myqsl -cab
|
||||||
|
|
||||||
|
Fixed negative user cache lookup in std_validade so that it actually
|
||||||
|
works for users being authenticated thru basic auth, especially if
|
||||||
|
they're authenticating in outer user folders -- rochael
|
||||||
|
|
||||||
|
Made smbAuthSource catch NetBIOSTimeout errors during authentication -- rochael
|
||||||
|
|
||||||
|
Fixed dtml/mainUser.dtml to be virtualhost-sensitive when displaying user
|
||||||
|
icons -- rochael
|
||||||
|
|
||||||
|
Updated UZG per user request. Fixed numbering, added information about
|
||||||
|
addition parameters like Negative Caching.
|
||||||
|
|
||||||
|
Changes for 0.10.9
|
||||||
|
|
||||||
|
Made dummyZBabelTag compatible to replace the NoBabel in OrderedFolder
|
||||||
|
while keeping its functionality in XUF -- cab
|
||||||
|
|
||||||
|
Changed _doAddUser, _doChangeUser to work with the public interface for
|
||||||
|
userfolders introduced in Zope2.5. Optional keyword arguments can now
|
||||||
|
be passed to _doAddUser and _doChangeUser.
|
||||||
|
|
||||||
|
PropertySource: Please note that createUser and updateUser, when called
|
||||||
|
from _doAddUser and _doChangeUser, will no longer be passed a REQUEST,
|
||||||
|
but a mapping with items from REQUEST updated with those from the
|
||||||
|
optional keyword arguments. -- pj
|
||||||
|
|
||||||
|
Fixed the problem with upgrading from 0.10.7 and below that didn't
|
||||||
|
account for existing XUF's not having a MessageDialog in their
|
||||||
|
contents. Now unless specificy replace it will use the MessageDialog
|
||||||
|
provided. Added how to do that to FAQ and README.Membership --cab
|
||||||
|
|
||||||
|
Made docLoginRedirect provide an absolute URL --bmh
|
||||||
|
|
||||||
|
MessageDialog in common no longer uses mangage_page_header and
|
||||||
|
mangage_page_footer v--cab
|
||||||
|
|
||||||
|
Changes for 0.10.8
|
||||||
|
|
||||||
|
Added the ability for members to change properties, and a default page
|
||||||
|
in the README.Membership to show how to do it --cab
|
||||||
|
|
||||||
|
MessageDialog is now an object in the ZODB that can be changed to fit
|
||||||
|
the site --cab
|
||||||
|
|
||||||
|
Now with 100% guaranteed race-condition-free UserCache goodness! Those
|
||||||
|
subclassing XUFUser, you will have to change your code. See User.py
|
||||||
|
for details. --mb
|
||||||
|
|
||||||
|
zodbBTreePropSource was returning None instead of the requested
|
||||||
|
default value, when called with (e.g.) someuser.getProperty('shoesize',13).
|
||||||
|
(Other property sources didn't have that bug.)
|
||||||
|
--davidc@debian.org
|
||||||
|
|
||||||
|
The tutorial loginform was wrong for Membership in README.Membership
|
||||||
|
|
||||||
|
Seems delProperty has never worked.. fixed --akm
|
||||||
|
Seems delProperty for pgPropSource has never worked.. fixed --akm
|
||||||
|
|
||||||
|
Fixed Basic Auth not auth problem. --akm
|
||||||
|
Fixed Basic Auth not cache problem. --akm
|
||||||
|
Fixed Cached Users bypassing some auth checks. --akm
|
||||||
|
|
||||||
|
Added usPropSource, which allows users to supply property methods TTW.
|
||||||
|
--bmh
|
||||||
|
|
||||||
|
Changes for 0.10.7
|
||||||
|
|
||||||
|
PropertyEditor had a typo in dtml and was casting int to None. --zxc
|
||||||
|
|
||||||
|
BasicAuth is now broken the other way, it'll allow any user to validate
|
||||||
|
with any password. --akm
|
||||||
|
|
||||||
|
Negative cache checking move was bogus. --akm
|
||||||
|
|
||||||
|
redirectToLogin didn't have a security declaration so 2.5.0 refused to
|
||||||
|
work in cookie mode *sigh* --akm
|
||||||
|
|
||||||
|
Fixed the 'None' object has no attribute 'load' setstate errors that
|
||||||
|
could crop up on propSources, and preemptively took care of the
|
||||||
|
authSources as well. Also fixed some of the weirder bugs relating to
|
||||||
|
user object acquisition context. --mb
|
||||||
|
|
||||||
|
Bug fixes from sf applied. --akm
|
||||||
|
|
||||||
|
Changes for 0.10.6
|
||||||
|
|
||||||
|
dummyZBabelTag used the python 2 re, which broke installations using
|
||||||
|
python 1.5 which still used the now deprecated regex, changed it to
|
||||||
|
catch the exception and use regex instead for python 1.5, else still
|
||||||
|
use re --cab
|
||||||
|
|
||||||
|
The redirectToLogin without Membership had a little logic problem where it
|
||||||
|
would basically garantee the existence of a query string, with at least a
|
||||||
|
lonely question mark even when there was no query string in the original
|
||||||
|
URL --rochael
|
||||||
|
|
||||||
|
smbAuthSource needed to cast NULL role properties to an empty list --akm
|
||||||
|
|
||||||
|
smbAuthSource had some dodgey zLOGing in it. --akm
|
||||||
|
|
||||||
|
smbAuthSource had some methods that should return [] instead of None. --akm
|
||||||
|
|
||||||
|
s/postgres/RADIUS/ in the radiusAuthSource DTML --akm
|
||||||
|
|
||||||
|
cookie_validate no longer pulls you from the cache if you're
|
||||||
|
logging in (which means your cookie wouldn't get set). --akm
|
||||||
|
|
||||||
|
Cookies are no longer expired if you're successfully authenticated but
|
||||||
|
merely unauthorized. --mb
|
||||||
|
|
||||||
|
Basic auth resynched with standard user folder, trying to fix
|
||||||
|
some basic auth issues. --akm.
|
||||||
|
|
||||||
|
Negative cache checking now performed outside of the two specific
|
||||||
|
validate methods. --akm.
|
||||||
|
|
||||||
|
A fairly innocuous print debug statement turned into a zLOG at error
|
||||||
|
level, removed --akm.
|
||||||
|
|
||||||
|
Clean up smbAuthSource log messages, and quieten. Only truly
|
||||||
|
exceptional cases are now logged above BLATHER. --mb
|
||||||
|
|
||||||
|
Changes for 0.10.5
|
||||||
|
|
||||||
|
Membership redirecting to login was still broken. It should be better
|
||||||
|
now (twice) --akm
|
||||||
|
|
||||||
|
logout() wasn't clearing the advanced cookie. --akm
|
||||||
|
|
||||||
|
Negative Cache Value wasn't being passed through to the XUF constructor. --akm
|
||||||
|
Log Users Out DTML code was broken, should work now. --akm
|
||||||
|
|
||||||
|
The User object now contains the authSource as well as the propSource,
|
||||||
|
making access to roles for custom User-objects possible. --dlk
|
||||||
|
|
||||||
|
Following akm's advice, fixed manage_beforeDelete to use two separate
|
||||||
|
try:except blocks to ensure that if cache-removal fails, deleting
|
||||||
|
the container.__allow_groups__ property is attempted. This should
|
||||||
|
fix the problem where deleted xuf instances remain as "ghost" products
|
||||||
|
causing interference with newer versions of xuf, and also fixes the
|
||||||
|
problem where deleting a xuf acl_users in a folder makes that folder
|
||||||
|
inaccessible. --dlk
|
||||||
|
|
||||||
|
Fixed cache_delete that was missing the "self" parameter in the method
|
||||||
|
defintion. --dlk
|
||||||
|
|
||||||
|
Fixed xcache_delete that was missing the "self" parameter in the method
|
||||||
|
definition --akm d8)
|
||||||
|
|
||||||
|
These previous two fix the problems with manage_beforeDelete, but, it
|
||||||
|
will stay the same for now --akm.
|
||||||
|
|
||||||
|
Fixed cache_deleteCookieCache that was missing the "self" parameter in
|
||||||
|
the method defintion. --dlk ;)
|
||||||
|
|
||||||
|
Changes for 0.10.4
|
||||||
|
|
||||||
|
The instructions for File Based Auth were incorrect in the UZG --akm
|
||||||
|
|
||||||
|
redirectToLogin was totally wrong for membership... --akm
|
||||||
|
docLogin was fixed for VHM use. --akm
|
||||||
|
|
||||||
|
Advanced Cookie Mode has changed so that it no longer sends the username
|
||||||
|
and password. Instead a hash is used as a key into a module level cache.
|
||||||
|
This should be 100% more secure than standard cookie mode, and removes
|
||||||
|
the stupid back doors I enabled in the previous version. This work was
|
||||||
|
based on conversations I had with Stuart Bishop (I basically lifted
|
||||||
|
the hashing scheme from GUF). This makes use of the Module level cache
|
||||||
|
code. --akm
|
||||||
|
|
||||||
|
There was a code cleanup and a slight reorganisation of some files. --akm
|
||||||
|
|
||||||
|
The main User Object has migrated to XUFUser and simarly with the
|
||||||
|
AnonUser. There is now an empty [Anon]User class that has XUFUser as
|
||||||
|
it's base. This allows people to create custom User Objects without
|
||||||
|
jumping through hoops (and simplifies maintaining patches) --akm
|
||||||
|
|
||||||
|
Cache Code has changed again. Now there is a module level cache, so
|
||||||
|
that auth data is shared between threads for a single XUF (thanks to
|
||||||
|
Stuart Bishop for an enlightening discussion on this and other issues,
|
||||||
|
and thanks to Chris McDonough for talking me through setting up module
|
||||||
|
level globals [and sending me some code to work from]) --akm
|
||||||
|
|
||||||
|
A Negative User Cache now exists. This is only generally useful for
|
||||||
|
use with remote auth sources where repeatedly trying to auth non-existant
|
||||||
|
users is very expensive (where they are authed at a higher level).
|
||||||
|
You can enable this on creation or from the parameters screen (positive
|
||||||
|
time in seconds enables). --akm
|
||||||
|
|
||||||
|
Domain checking code finally removed. --akm
|
||||||
|
|
||||||
|
zodbBTreePropSource changed to be friendlier about users that exist
|
||||||
|
in remote locations (i.e. aren't create as such through the ZMI). -- akm
|
||||||
|
|
||||||
|
Changed some 'print's in the code to use zLOG.LOG
|
||||||
|
instead. Files affected so far (more to follow): -- rochael
|
||||||
|
|
||||||
|
* exUserFolder.py
|
||||||
|
* basicMemberSource/basicMemberSource.py
|
||||||
|
* zodbBTreePropSource/zodbBTreePropSource.py
|
||||||
|
* zodbPropSource/zodbPropSource.py
|
||||||
|
|
||||||
|
Changed a couple things in smbAuthSource.py: -- rbanffy
|
||||||
|
|
||||||
|
* Method _authenticate_retry now logs several kinds of information
|
||||||
|
for debugging and diagnostics.
|
||||||
|
|
||||||
|
* Modified socket.error handling in _authenticate_retry: changed
|
||||||
|
"raise" to "return 0".
|
||||||
|
|
||||||
|
* Since this generated more problems (failed authentications) than
|
||||||
|
it solved (our impression it was not right not to return 0 in an
|
||||||
|
auth fail even due to a communications malfunction), we also
|
||||||
|
changed socket.error handling to retry no mather what errno tells
|
||||||
|
us (it said different things for the same problem under Windows
|
||||||
|
and Linux).
|
||||||
|
|
||||||
|
* In order to prevent infinite retries, changed retry handling a
|
||||||
|
bit. It now retries 3 times. Real-use data will tell us if we
|
||||||
|
should increase or not retries. To better convey the meaning of
|
||||||
|
the parameter, changed "retry_depth" to "retries". I strongly
|
||||||
|
advise the use of credential caching with smbAuthSource, tough, as
|
||||||
|
it reduces socket errors and load on the domain controllers.
|
||||||
|
|
||||||
|
Changes for 0.10.3.1
|
||||||
|
|
||||||
|
Readded support for I18N without ZBabel installation, somehow missed
|
||||||
|
during the transition to SF CVS.
|
||||||
|
|
||||||
|
Some text changes as well as an update to the dictionary while we're
|
||||||
|
at it. No functional changes for this release though.
|
||||||
|
|
||||||
|
Changes for 0.10.3
|
||||||
|
|
||||||
|
Missed a few LoginRequireds.
|
||||||
|
|
||||||
|
Fixed a bug with __allow_groups__ not being set after paste
|
||||||
|
(probably also not after import).
|
||||||
|
|
||||||
|
The sources are now sorted by name in the drop down box..
|
||||||
|
|
||||||
|
a BTree version of zodbAuthSource
|
||||||
|
a BTree version of zodbPropSource
|
||||||
|
|
||||||
|
These aren't really all that different to the originals that were
|
||||||
|
provided by Alex, but, they use BTrees instead of PersistentMappings,
|
||||||
|
and try to avoid various persistence problems associated with dicts.
|
||||||
|
Both versions will continue to be supported.
|
||||||
|
|
||||||
|
Patches from SF applied.
|
||||||
|
|
||||||
|
Advanced Cookie Mode added.
|
||||||
|
This mode adds a rotor cipher around the cookie. A secret is provided
|
||||||
|
in order to encode the cookie. The username and password are placed
|
||||||
|
within a small class which is pickled and then encrypted and then
|
||||||
|
base64 encoded for transport. There is also a timestamp inside the cookie,
|
||||||
|
so the ultra-paranoid of you can rotate the cookie based on the timestamp
|
||||||
|
inside.
|
||||||
|
|
||||||
|
Abstracted out the setting and decoding of cookies.
|
||||||
|
|
||||||
|
Changes for 0.10.2
|
||||||
|
|
||||||
|
all raise 'LoginRequired' <- raise 'Unauthorized'
|
||||||
|
|
||||||
|
Raising unauthorizes breaks a million things. CMF people can just
|
||||||
|
put up with configuring their portal properly.
|
||||||
|
|
||||||
|
Radius resynced with version from sourceforge.
|
||||||
|
manage_tabs redone to be ZBabel'd and to look like standard tabs.
|
||||||
|
|
||||||
|
German Language added to the ZBabel dictionary.
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.10.1
|
||||||
|
|
||||||
|
all raise 'LoginRequired' -> raise 'Unauthorized'
|
||||||
|
|
||||||
|
Bug in etcAuthSource listUsers fixed,
|
||||||
|
and cryptPassword also fixed to get the actual salt.
|
||||||
|
|
||||||
|
Zope 2.4.3 has dicked with security settings again.. I've had a round
|
||||||
|
of permission whacking.
|
||||||
|
|
||||||
|
Buggy handling of empty role lists was fixed.
|
||||||
|
|
||||||
|
Change to smbAuthSource to use string.lower on usernames for
|
||||||
|
python 1.5.2 compatibility?
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.10.0
|
||||||
|
|
||||||
|
Added explicit roles for manage_editUser and friends, to allow
|
||||||
|
the "Manage users" permission to be useful to non-Manager Users.
|
||||||
|
Thanks to Heimo Laukkanen <huima@fountainpark.org> for reporting this
|
||||||
|
one.
|
||||||
|
|
||||||
|
zodbAuthSource made more persistent <alex@quad.com.ar>
|
||||||
|
zodbPropSource was blowing when deleting temporary properties.
|
||||||
|
|
||||||
|
XUF is now ZBabel'd which means you can view XUF in different languages
|
||||||
|
for logging in and installation, if your browser locale is set up.
|
||||||
|
You will need the latest ZBabel installed. The translation file is in the
|
||||||
|
I18N directory.
|
||||||
|
|
||||||
|
Import this (using Import/Export in ZODB) at the same level as your
|
||||||
|
ZBabelTower, and then import it from ZBabel. If you have ZBabel installed,
|
||||||
|
but, your application can't find a ZBabelTower, because of a bug in the
|
||||||
|
current dtml-fish tag, you might experience some problems. This ZBabel
|
||||||
|
bug should be fixed sometime soon.
|
||||||
|
|
||||||
|
You do not need ZBabel installed to run XUF, XUF installs a dummy
|
||||||
|
interface for ZBabel so that XUF can continue to run (sorry folks it
|
||||||
|
defaults to Australian English).
|
||||||
|
|
||||||
|
getUserNames() was returning the wrong stuff (notably affected TheJester's
|
||||||
|
WorkOrders Product)
|
||||||
|
|
||||||
|
There is a now an 'Advanced Postgres' Auth Source that uses a seperate
|
||||||
|
Roles table and a 'more relational' layout. The schema is with the
|
||||||
|
auth source in pgAuthSourceAlt. Contributed by
|
||||||
|
Adam Manock <abmanock@earthlink.net>
|
||||||
|
|
||||||
|
If you had a membership source and had specified a login page, XUF was
|
||||||
|
still using the stock docLogin instead of the membership specified page
|
||||||
|
(for redirectToLogin, exceptions still raise the docLogin).
|
||||||
|
|
||||||
|
I changed the icon to something a *little* less hideous
|
||||||
|
|
||||||
|
Leonardo Rochael Almeida <leo@hiper.com.br> made the following changes
|
||||||
|
to smbAuthSource
|
||||||
|
|
||||||
|
* Added a 'winsserver' constructor parameter and a '_winsserver'
|
||||||
|
instance variable to the 'smbAuthSource' class. This variable should
|
||||||
|
be the empty string, meaning that the authenticaton host will be
|
||||||
|
looked up by broadcast, or an IP address string pointing to a WINS
|
||||||
|
server.
|
||||||
|
|
||||||
|
* Modified the dtml templates to ask for the above mentioned WINS
|
||||||
|
server (and also to replace 'Add' with 'Change' in
|
||||||
|
'manage_editsmbAuthSourceForm').
|
||||||
|
|
||||||
|
* Refactored the smbAuthSource class to isolate all smb interaction
|
||||||
|
inside well defined methods.
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.9.0
|
||||||
|
|
||||||
|
Messages are now sent back to the docLogin form. There's a file called
|
||||||
|
LoginRequiredMessages.py where the messages are kept for now (it might
|
||||||
|
end up a run-time configurable thing later).
|
||||||
|
|
||||||
|
There's a new docLogin.dtml file on disk that shows how to use the new
|
||||||
|
messages. Because docLogin is in the ZODB this won't be automatically
|
||||||
|
upgraded.
|
||||||
|
|
||||||
|
Idle Session Timeouts are in (this is the reason for the minor bump).
|
||||||
|
If you flick the switch, then users are forced back to the login form
|
||||||
|
(with a message saying their session timed out), when they're removed
|
||||||
|
from the cache.
|
||||||
|
|
||||||
|
I made some adjustments to the tabs on the management interface because
|
||||||
|
they were too big, and I cleaned it up a bit for times when they run
|
||||||
|
together.
|
||||||
|
|
||||||
|
The internal API was inconsistent, so that's been updated.
|
||||||
|
AuthSources no longer need to provide getUsers(), it was never
|
||||||
|
being called anyway since exUserFolder built it's own.
|
||||||
|
listUsers now returns the same data as listOneUser, this is used in
|
||||||
|
other places as if it were a list of listOneUser calls.
|
||||||
|
|
||||||
|
Fixed pgAuthSource to deal with NULL rather than empty roles
|
||||||
|
columns (legacy columns).
|
||||||
|
|
||||||
|
Changed Home Directory creation to use copy & paste functions to
|
||||||
|
copy the skeleton data.
|
||||||
|
|
||||||
|
Changes for 0.8.5
|
||||||
|
|
||||||
|
I forgot to update the schema file for userproperties to reflect
|
||||||
|
the temporary properties flag.
|
||||||
|
|
||||||
|
Checks for existing cache weren't being performed before removing users
|
||||||
|
from it, when their data was updated.
|
||||||
|
|
||||||
|
Reversed the order for checking in cookie_validate, to allow logging
|
||||||
|
in as a new user, when session tracking was on. Also now you can
|
||||||
|
login as a different user, without logging out first, which might
|
||||||
|
be useful to some people.
|
||||||
|
|
||||||
|
etcAuthSource now looks for the correct salt from the file for
|
||||||
|
encrypting the user supplied password
|
||||||
|
|
||||||
|
Changes for 0.8.4
|
||||||
|
|
||||||
|
Activating Session Tracking and then adding a new user when there
|
||||||
|
were none in the XUF was broken.
|
||||||
|
|
||||||
|
Changes for 0.8.3
|
||||||
|
|
||||||
|
The idle users are flushed from the cache when you ask for the list
|
||||||
|
of cache users (since it's iterating over the whole list anyway). So
|
||||||
|
you can manually clear your cache by looking at the Cache Stats page.
|
||||||
|
|
||||||
|
If you display the list of logged in users on your site, then your cache
|
||||||
|
will be flushed for you automagically.
|
||||||
|
|
||||||
|
Allowed a destination to be sent to redirectToLogin to allow you to
|
||||||
|
manually override the destination after logging in.
|
||||||
|
|
||||||
|
Added in a __setstate__ for pgPropSource to deal with new ZSQL Methods
|
||||||
|
being added.
|
||||||
|
|
||||||
|
Changes for 0.8.2
|
||||||
|
A number of bugs related to temp properties fixed in pgPropSource
|
||||||
|
|
||||||
|
FTP Access to folders protected with cookie_mode has been fixed, it
|
||||||
|
now reverts to std_auth (which handles the FTP connection fine), since
|
||||||
|
FTP auths are handled by getting a "Basic" auth tag coming through, which
|
||||||
|
should never happen in cookie mode.
|
||||||
|
|
||||||
|
This has the knock-on effect of authenticating users that auth from a
|
||||||
|
higher acl_users that doesn't use cookies, 'more' correctly now. Which is
|
||||||
|
if you have a user defined above, and in XUF and the XUF user has less
|
||||||
|
permissions, it'll 401 you if you don't have permissions locally
|
||||||
|
(which is the correct behaviour). This bit me in the arse when I changed it,
|
||||||
|
and I'm still leaving it this way. d8)
|
||||||
|
|
||||||
|
Users are now flushed from the cache when you edit them (in case you changed
|
||||||
|
roles), so that new roles should take effect immediately.
|
||||||
|
|
||||||
|
The credential cache now uses the (Zope) builtin BTree Module for caching
|
||||||
|
rather than the AVL Tree implementation. There was a nasty issue with users
|
||||||
|
appearing multiple times in the AVL Tree which sucked.
|
||||||
|
|
||||||
|
There is a report of the Radius Auth Source being broken (most likely
|
||||||
|
by me), if your radius source stops working, you can try copying the
|
||||||
|
py-radius.py file from sourceforge over the top of radius.py. If someone
|
||||||
|
gives me a traceback, I can fix it. I don't seem to be having problems,
|
||||||
|
but, I don't have a full time RADIUS source either.
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.8.1
|
||||||
|
|
||||||
|
A bug in _doAddUser was fixed
|
||||||
|
A bug in the User Object unconditionally calling the prop source was fixed.
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.8.0
|
||||||
|
|
||||||
|
Experimental "Session Tracking" added (why is it called that? we don't really
|
||||||
|
track anything, just associate arbitrary data with anonymous users).
|
||||||
|
This relies on the credential cache being active. Your session will
|
||||||
|
automatically expire when the anonymous user is so idle that they are
|
||||||
|
expired from the cache. This is not currently acceptable (to me), but,
|
||||||
|
it might be to other people, I await feedback on how sessions should expire
|
||||||
|
gracefully.
|
||||||
|
|
||||||
|
Updated the README.txt file to point at the UZG and to explain the
|
||||||
|
version numbering system.
|
||||||
|
|
||||||
|
All this time you couldn't delete properties from a user... who knew?
|
||||||
|
It's fixed now.
|
||||||
|
|
||||||
|
Temporary properties now available, you can setTempProperty() on a
|
||||||
|
user object, and also flushTempProperties() on a user object.
|
||||||
|
Temporary properties are accessed like normal properties, and can be
|
||||||
|
deleted in the same way. flushTempProperties is there to do a quick
|
||||||
|
flush of all the crap you might have inserted (useful for sessions).
|
||||||
|
If your user is flushed from the cache, then all temp properties will
|
||||||
|
also be removed at that point.
|
||||||
|
|
||||||
|
Propsource providers should look at the new temp properties stuff and
|
||||||
|
update accordingly.
|
||||||
|
|
||||||
|
Alex provided a whole heap of patches to make basicMembership more usable,
|
||||||
|
well make it actually work.
|
||||||
|
|
||||||
|
Matt Behrens supplied patches to prevent null logins and to allow case
|
||||||
|
insensitive logins for smbAuthSource
|
||||||
|
|
||||||
|
Added a basic FAQ.
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.7.10
|
||||||
|
|
||||||
|
Active Users type functionality was added. The new function is called
|
||||||
|
getUserCacheUsers(). It returns a list of dicts;
|
||||||
|
|
||||||
|
{'username': theusername, 'lastAccessed': float_value}
|
||||||
|
|
||||||
|
lastAccessed represents the last time the user touched something.
|
||||||
|
The Cache Stats page shows an example usage showing idle time (very cool
|
||||||
|
I think :-)
|
||||||
|
|
||||||
|
The logout method was not correctly removing users from the cache,
|
||||||
|
although the cookie was removed, so logins were still enforced. I'm not
|
||||||
|
sure of any side-effects related to it, but,
|
||||||
|
|
||||||
|
Some permissions were a little too liberal, including allowing arbitrary
|
||||||
|
users to set and get Properties on the acl_users folder.
|
||||||
|
|
||||||
|
Copy/Paste support for pasting exUserFolders into the root was added.
|
||||||
|
I'm not sure I like the way this is done. I haven't found any side effects
|
||||||
|
so far, but, just be wary. Adding an exUserFolder to the root becomes
|
||||||
|
semi-trivial now. Create one in a sub-folder. Login as the emergency user.
|
||||||
|
CUT the exUserFolder. Delete the standard acl_users folder. Paste exUserFolder.
|
||||||
|
You should be away. At least it worked fine for me... YMMV
|
||||||
|
|
||||||
|
_doChangeUser and _doDelUsers added so users can be altered and deleted
|
||||||
|
like for Standard UserFolder.
|
||||||
|
|
||||||
|
_createInitialUser added so there should always be your initUser (hopefully)
|
||||||
|
when you create your exUserFolder.
|
||||||
|
|
||||||
|
Emergency User checking brought into line with Standard Folder
|
||||||
|
|
||||||
|
__creatable_by_emergency_user_ added and returns 1 to explicitly allow this.
|
||||||
|
|
||||||
|
Unenlightened Zopistas Guide updated to have a 'Recipe' like section.
|
||||||
|
Currently contains a section about adding exUserFolders from python.
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.7.9
|
||||||
|
|
||||||
|
RADIUS authSource had a problem with non-integers being extracted from
|
||||||
|
REQUEST (I wish someone at DC would fix this already). I worked around
|
||||||
|
this problem
|
||||||
|
|
||||||
|
Default port for RADIUS is now 1812 in line with the IANA sanctioned list.
|
||||||
|
|
||||||
|
Unenlightened Zopistas Guide to exUserFolder version 0.0 included,
|
||||||
|
covers installation and authentication sources, and the most common
|
||||||
|
configuration mistake (or misunderstanding).
|
||||||
|
|
||||||
|
I almost released with the daggy management screens all Purple or SkyBlue,
|
||||||
|
so consider yoursevles lucky. This would have been the "Blue" release.
|
||||||
|
|
||||||
|
Changes for 0.7.8
|
||||||
|
|
||||||
|
zodbPropSource had a bug that must have been there since 0.0.0 where
|
||||||
|
_p_changed wasn't being called on create, update, or delete user.
|
||||||
|
Thanks to Bouke Scheurwater for spotting that one.
|
||||||
|
|
||||||
|
Alex provided a number of patched to fix a whole bunch of goofy stuff
|
||||||
|
with Basic Member Source that was stupidly wrong.
|
||||||
|
|
||||||
|
Matt Behrens provided a patch to allow emergency user to own exUserFolders
|
||||||
|
and some of the sources. I've grudgingly updated all the sources to allow
|
||||||
|
this. It's just a hey nonny nonny to people using it as a root authenticator
|
||||||
|
now.
|
||||||
|
|
||||||
|
Matt Behrens also provided a patch to fix 'broken pipe' problems with
|
||||||
|
smbAuthSource.
|
||||||
|
|
||||||
|
pySMB is now at 0.2 for smbAuthSource WARNING: This will try to use DES
|
||||||
|
encrypted passwords. Apparently it should be ok if your server doesn't want
|
||||||
|
them. However if it breaks, unpack the pySMB distribution in the
|
||||||
|
smbAuthSource directory, there are registry examples there to turn
|
||||||
|
it off. It unfortunately needs the mxCrypto tools for encrypted passwords
|
||||||
|
to work. When I've got a bit more time, I'll see if I can make it use
|
||||||
|
crypt or fcrypt if available instead.
|
||||||
|
|
||||||
|
Explicit checks for the emergency user were placed into the cookie_validate
|
||||||
|
routines. I suspect this may have been the cause of some grief with people
|
||||||
|
doing weird things like trying to make it the root auth folder.
|
||||||
|
|
||||||
|
Changes for 0.7.7
|
||||||
|
|
||||||
|
Some Auth sources had problems coping with no roles being selected when
|
||||||
|
a user was created from the management interface, the stock ones were fixed.
|
||||||
|
|
||||||
|
I screwed up some of the DTML, and forgot to change the loading of two of
|
||||||
|
the methods from the dtml directory.
|
||||||
|
|
||||||
|
NO MORE TRACEBACKS ON LOGIN FORMS, there is a little redirector dtml file
|
||||||
|
dtml/docLoginRedirect that redirects to acl_users/docLogin with destination
|
||||||
|
set to take them back to where they were going. If you have a custom loginPage
|
||||||
|
change the redirector dtml to point to your new page.
|
||||||
|
|
||||||
|
standard_html swapped for manage_page on Management Pages. Hopefully
|
||||||
|
this doesn't break someone with an old copy of Zope.
|
||||||
|
|
||||||
|
Credential Caching is now available by default for all Authentication Sources,
|
||||||
|
upgrading installs will get this defaulted to 0 for no caching. You can alter
|
||||||
|
the cache level from the Parameters Tab. Authors of external sources should
|
||||||
|
remove any internal auth caching they're doing, and allow the user to decide
|
||||||
|
how long to cache the credentials for.
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.7.6
|
||||||
|
|
||||||
|
smbAuthSource included. Doesn't require any external libraries, or compiling.
|
||||||
|
Uses pySMB from Micheal Teo <michaelteo@bigfoot.com>
|
||||||
|
|
||||||
|
Changes for 0.7.5
|
||||||
|
The Management Interface now batches the user list by 10. This isn't
|
||||||
|
configurable at the moment (just change the dtml).
|
||||||
|
|
||||||
|
The code was re-organised slightly, with all the DTML moving into its
|
||||||
|
own directory for core.
|
||||||
|
|
||||||
|
radiusAuthSource added, but, is so far untested. It is a direct port of
|
||||||
|
ZRadius for GUF, but, I haven't had a chance to setup a RADIUS server to
|
||||||
|
test it out.
|
||||||
|
|
||||||
|
You can add properties to a user from the management interface.
|
||||||
|
|
||||||
|
List Properties on users can be added and edited, if I can work out a decent
|
||||||
|
way to edit Dicts/Mappings, I'll add that feature in.
|
||||||
|
|
||||||
|
This paves the way for defining a set of properties in the Membership
|
||||||
|
source, so it can create a Signup and Edit page for you automatically.
|
||||||
|
You will also be able to specify which properties the user can edit, or
|
||||||
|
roles required to edit a property, this will be in a later release though.
|
||||||
|
|
||||||
|
pgPropSource was updated to take into account non-scalar types, and now
|
||||||
|
pickles all data going into the database, this means ints will stay as ints,
|
||||||
|
et al.
|
||||||
|
There is code in there to cope with older properties coming out as strings.
|
||||||
|
The Schema remains the same.
|
||||||
|
|
||||||
|
Changes for 0.7.2
|
||||||
|
Changes to make it work with older version of python
|
||||||
|
Some minor bug fixes for membership.
|
||||||
|
|
||||||
|
Changes for 0.7.1
|
||||||
|
DTML Change for cmfPropSource
|
||||||
|
|
||||||
|
Changes for 0.7.0
|
||||||
|
exUserFolder was a little too liberal in removing its cruft, this is now
|
||||||
|
fixed.
|
||||||
|
|
||||||
|
cmfPropSource was provided by Alan Runyan which is a layer around the CMF
|
||||||
|
property stuff. It's conditionally imported, so if you don't have CMF
|
||||||
|
installed you don't need to worry that'll it'll break.
|
||||||
|
|
||||||
|
Property Sources are optional, and there is a NULL Property Source for this
|
||||||
|
purpose.
|
||||||
|
|
||||||
|
Membership hooks, and a rough start at membership (basicMemberSource),
|
||||||
|
which has some usable functionality (you MUST read README.Membership before
|
||||||
|
using this).
|
||||||
|
|
||||||
|
Membership Sources are optional and there is a NULL Membership Source for
|
||||||
|
this purpose.
|
||||||
|
|
||||||
|
|
||||||
|
Changes for 0.6.2
|
||||||
|
exUserFolder was leaving cruft around when it was being deleted from
|
||||||
|
Folders. The cruft should now be obliterated if you delete an exUserFolder.
|
||||||
|
|
||||||
|
Changes for 0.6.1
|
||||||
|
Ownership tab enabled, for those sick monkeys that want to use it as a root
|
||||||
|
Folder (there are some).
|
||||||
|
|
||||||
|
fcrypt got the __init__.py that was missing from the 0.6.0 release
|
||||||
|
zodbAuthSource updated to pull in fcrypt if crypt was missing.
|
||||||
|
|
||||||
|
Changes for 0.6.0
|
||||||
|
|
||||||
|
Updated for 2.4.1 / Python 2.1
|
||||||
|
Bug in pgPropSource not deleting users from the property cache fixed.
|
||||||
|
Bug with Local Roles not getting what it expected fixed.
|
||||||
|
Alex Verstraeten provided zodbAuthSource, there's a README.zodbAuthSource,
|
||||||
|
and the same README inside the zodbAuthSource directory.
|
||||||
|
fcrypt is now included and used if crypt cannot be imported. More information
|
||||||
|
on fcrypt can be found at http://home.clear.net.nz/pages/c.evans/sw/. This
|
||||||
|
should help particularly Windows users a lot.
|
||||||
|
Rudimentary API doc included.
|
||||||
|
|
||||||
|
Changes for 0.5.0
|
||||||
|
|
||||||
|
A serious bug in zodbPropSource was fixed.
|
||||||
|
|
||||||
|
There is now the option of providing a 'Remote Auth' function for
|
||||||
|
validating. This allows things like IMAP/LDAP auth sources to do their
|
||||||
|
authentication, since they don't return passwords you can use in general.
|
||||||
|
|
||||||
|
There's already a 3rd Party solution that provides IMAP/POP3 authentication,
|
||||||
|
using the new API.
|
||||||
|
|
||||||
|
Changes for 0.4.6
|
||||||
|
|
||||||
|
Minor dtml hacks
|
||||||
|
|
||||||
|
Changes for 0.4.5
|
||||||
|
|
||||||
|
Hooks for 'editing' Authentication and Property Sources were added, along
|
||||||
|
with the relevant methods in each of the sources.
|
||||||
|
|
||||||
|
The management interfaces got a little overhaul, just to make them
|
||||||
|
a little different (yes I know everything I do looks the same). The two
|
||||||
|
I didn't want to mess with still have the acquired management interfaces.
|
||||||
|
|
||||||
|
A fix for the ZODB Property Source which was missing a few methods.
|
||||||
|
|
||||||
|
Changes for 0.4.0
|
||||||
|
|
||||||
|
Based on an idea from Martin von Loewis, I added in support for defining
|
||||||
|
roles for etcAuthSource. This basically uses the current Prop source to
|
||||||
|
store a 'roles' property. The default role is still there as well for
|
||||||
|
those of you who might be using it.
|
||||||
|
|
||||||
|
Changes for 0.3.0
|
||||||
|
|
||||||
|
Adrien Hernot noticed that properties for new users using zodbPropSource
|
||||||
|
were causing havoc, and that the version.txt file was completely wrong.
|
||||||
|
Andreas also noticed the version.txt was wrong.
|
||||||
|
|
||||||
|
I've been bugged enough by the pair of them to change the single +=
|
||||||
|
into 1.5.2 compliant syntax.
|
||||||
|
|
||||||
|
I don't make any claims about it working under 1.5.2 though.
|
||||||
|
|
||||||
|
Changes for 0.2.0
|
||||||
|
|
||||||
|
Even more embarassment...
|
||||||
|
|
||||||
|
Andreas Heckel provided fixes for some stupid things I left out including;
|
||||||
|
|
||||||
|
o Fixing the way I was handling multiple roles coming out of the database
|
||||||
|
o The wrong icon in the user display
|
||||||
|
o Alerting me to the fact that pgPropSource didn't actually have a
|
||||||
|
deleteUsers hook
|
||||||
|
o Providing a schema for automatically deleting properties in postgres
|
||||||
|
if you delete a user from the auth source (you have to be using both
|
||||||
|
pg sources for this to work, and they'd have to be in the same database)
|
||||||
|
I've put Andreas schema into the distribution, if you want to use
|
||||||
|
exUserFolder as a straight pgUserFolder, you'll also need to edit
|
||||||
|
exUserFolder.py and comment out the line indicated in deleteUsers()
|
||||||
|
|
||||||
|
Changes for 0.1.0
|
||||||
|
|
||||||
|
Pretty embarassing really.
|
||||||
|
|
||||||
|
M. Adam Kendall (DaJoker) found some stupid things in the 0.0.0 release
|
||||||
|
including the fact you couldn't edit user properties, or update them,
|
||||||
|
or actually change a user in anyway.
|
||||||
|
|
||||||
|
I also discovered I was resetting the password to empty if you left it
|
||||||
|
empty..
|
4
ZopeProducts/exUserFolder/CryptoSources/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import pass_crypt
|
||||||
|
import pass_md5
|
||||||
|
import pass_sha
|
||||||
|
import pass_plain
|
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
35
ZopeProducts/exUserFolder/CryptoSources/fcrypt/ChangeLog
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
2001-05-05 Carey Evans <careye@spamcop.net>
|
||||||
|
|
||||||
|
* fcrypt.py: Add module doc string for pydoc, and other globals
|
||||||
|
for pydoc as well. Add __all__ for Python 2.1, and add
|
||||||
|
underscores to the front of private variables and functions.
|
||||||
|
(_set_key): Remove overly clever copying of globals into default
|
||||||
|
parameters, explicitly copying _shift2 and _skb before the loop.
|
||||||
|
(_body): Copy _SPtrans explicitly, as above. Remove CR_ENCRYPT
|
||||||
|
inline function, and reroll unrolled loop using the contents of
|
||||||
|
this function. Result: more readable code, and a 400% speedup!
|
||||||
|
(crypt): Add doc string for pydoc and doctest.
|
||||||
|
(_test): New function for doctest.
|
||||||
|
|
||||||
|
* setup.py: Add fields for PKG-INFO metadata.
|
||||||
|
|
||||||
|
* README: Add URL of distutils installation manual.
|
||||||
|
|
||||||
|
* LICENSE: Add note about license on fcrypt.py being the union of
|
||||||
|
my license on the Python code and Eric Young's on the original C.
|
||||||
|
|
||||||
|
2001-03-24 Carey Evans <careye@spamcop.net>
|
||||||
|
|
||||||
|
* setup.py: Move license to separate file. Change email address
|
||||||
|
to SpamCop forwardder. Update version to 1.1.
|
||||||
|
|
||||||
|
* fcrypt.py: Update license text and email address.
|
||||||
|
(crypt): Fix bug where passwords longer than eight characters were
|
||||||
|
not truncated.
|
||||||
|
|
||||||
|
* README: Update crypt module URL. Remove license text, and add
|
||||||
|
pointer to LICENSE file. Update email address.
|
||||||
|
|
||||||
|
* MANIFEST.in: Add LICENSE, ChangeLog and MANIFEST.in.
|
||||||
|
|
||||||
|
* LICENSE: New file.
|
77
ZopeProducts/exUserFolder/CryptoSources/fcrypt/LICENSE
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
fcrypt.py copyrights and license
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
The Python code by Carey Evans has the following license, which is the
|
||||||
|
original Python license with the serial numbers filed off, and the
|
||||||
|
restrictions on advertising removed.
|
||||||
|
|
||||||
|
Copyright (C) 2001, 2001 Carey Evans <careye@spamcop.net>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software and its
|
||||||
|
documentation for any purpose and without fee is hereby granted,
|
||||||
|
provided that the above copyright notice appear in all copies and that
|
||||||
|
both that copyright notice and this permission notice appear in
|
||||||
|
supporting documentation.
|
||||||
|
|
||||||
|
CAREY EVANS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||||
|
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
|
||||||
|
EVENT SHALL CAREY EVANS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||||
|
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
|
||||||
|
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
The original C code on which this module was based has the following
|
||||||
|
more restrictive license, so the source for fcrypt.py should be
|
||||||
|
considered to be covered by the union of my license and Eric Young's.
|
||||||
|
|
||||||
|
This library is free for commercial and non-commercial use as long as
|
||||||
|
the following conditions are aheared to. The following conditions
|
||||||
|
apply to all code found in this distribution, be it the RC4, RSA,
|
||||||
|
lhash, DES, etc., code; not just the SSL code. The SSL documentation
|
||||||
|
included with this distribution is covered by the same copyright terms
|
||||||
|
except that the holder is Tim Hudson (tjh@mincom.oz.au).
|
||||||
|
|
||||||
|
Copyright remains Eric Young's, and as such any Copyright notices in
|
||||||
|
the code are not to be removed.
|
||||||
|
If this package is used in a product, Eric Young should be given attribution
|
||||||
|
as the author of the parts of the library used.
|
||||||
|
This can be in the form of a textual message at program startup or
|
||||||
|
in documentation (online or textual) provided with the package.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
3. All advertising materials mentioning features or use of this software
|
||||||
|
must display the following acknowledgement:
|
||||||
|
"This product includes cryptographic software written by
|
||||||
|
Eric Young (eay@mincom.oz.au)"
|
||||||
|
The word 'cryptographic' can be left out if the rouines from the library
|
||||||
|
being used are not cryptographic related :-).
|
||||||
|
4. If you include any Windows specific code (or a derivative thereof) from
|
||||||
|
the apps directory (application code) you must include an acknowledgement:
|
||||||
|
"This product includes software written by Tim Hudson (tjh@mincom.oz.au)"
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
||||||
|
|
||||||
|
The licence and distribution terms for any publically available version or
|
||||||
|
derivative of this code cannot be changed. i.e. this code cannot simply be
|
||||||
|
copied and put under another distribution licence
|
||||||
|
[including the GNU Public Licence.]
|
@ -0,0 +1 @@
|
|||||||
|
include LICENSE ChangeLog MANIFEST.in
|
13
ZopeProducts/exUserFolder/CryptoSources/fcrypt/PKG-INFO
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Metadata-Version: 1.0
|
||||||
|
Name: fcrypt
|
||||||
|
Version: 1.2
|
||||||
|
Summary: The Unix password crypt function.
|
||||||
|
Home-page: http://home.clear.net.nz/pages/c.evans/sw/
|
||||||
|
Author: Carey Evans
|
||||||
|
Author-email: careye@spamcop.net
|
||||||
|
License: BSD
|
||||||
|
Description: A pure Python implementation of the Unix DES password crypt function,
|
||||||
|
based on Eric Young's fcrypt.c. It works with any version of Python
|
||||||
|
from version 1.5 or higher, and because it's pure Python it doesn't
|
||||||
|
need a C compiler to install it.
|
||||||
|
Platform: UNKNOWN
|
33
ZopeProducts/exUserFolder/CryptoSources/fcrypt/README
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
fcrypt.py
|
||||||
|
---------
|
||||||
|
|
||||||
|
This is a pure Python implementation of the Unix DES password crypt
|
||||||
|
function. It was ported from C code by Eric Young (eay@mincom.oz.au).
|
||||||
|
See the file LICENSE for copyright and license details.
|
||||||
|
|
||||||
|
This module is packaged with Distutils. If you have this installed,
|
||||||
|
or it came with your version of Python, you can install it by typing:
|
||||||
|
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
|
If not, you can just copy `fcrypt.py' into a directory on your Python
|
||||||
|
library path, or into the same directory as the program that wants to
|
||||||
|
use it.
|
||||||
|
|
||||||
|
For more information, see the documentation for Python's built-in
|
||||||
|
crypt module at:
|
||||||
|
|
||||||
|
http://www.python.org/doc/current/lib/module-crypt.html
|
||||||
|
|
||||||
|
Eric Young's fcrypt.c is available from:
|
||||||
|
|
||||||
|
ftp://ftp.psy.uq.oz.au/pub/Crypto/DES/
|
||||||
|
|
||||||
|
For more Distutils information, see:
|
||||||
|
|
||||||
|
http://www.python.org/doc/current/inst/inst.html
|
||||||
|
http://www.python.org/sigs/distutils-sig/
|
||||||
|
|
||||||
|
--
|
||||||
|
Carey Evans <careye@spamcop.net>
|
||||||
|
5 May 2001
|
@ -0,0 +1 @@
|
|||||||
|
import fcrypt
|
602
ZopeProducts/exUserFolder/CryptoSources/fcrypt/fcrypt.py
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
# fcrypt.py
|
||||||
|
|
||||||
|
"""Unix crypt(3) password hash algorithm.
|
||||||
|
|
||||||
|
This is a port to Python of the standard Unix password crypt function.
|
||||||
|
It's a single self-contained source file that works with any version
|
||||||
|
of Python from version 1.5 or higher. The code is based on Eric
|
||||||
|
Young's optimised crypt in C.
|
||||||
|
|
||||||
|
Python fcrypt is intended for users whose Python installation has not
|
||||||
|
had the crypt module enabled, or whose C library doesn't include the
|
||||||
|
crypt function. See the documentation for the Python crypt module for
|
||||||
|
more information:
|
||||||
|
|
||||||
|
http://www.python.org/doc/current/lib/module-crypt.html
|
||||||
|
|
||||||
|
The crypt() function is a one-way hash function, intended to hide a
|
||||||
|
password such that the only way to find out the original password is
|
||||||
|
to guess values until you get a match. If you need to encrypt and
|
||||||
|
decrypt data, this is not the module for you.
|
||||||
|
|
||||||
|
There are at least two packages providing Python cryptography support:
|
||||||
|
M2Crypto at <http://www.pobox.org.sg/home/ngps/m2/>, and amkCrypto at
|
||||||
|
<http://www.amk.ca/python/code/crypto.html>.
|
||||||
|
|
||||||
|
Functions:
|
||||||
|
|
||||||
|
crypt() -- return hashed password
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = 'Carey Evans <careye@spamcop.net>'
|
||||||
|
__version__ = '1.2'
|
||||||
|
__date__ = '6 May 2001'
|
||||||
|
__credits__ = '''michal j wallace for inspiring me to write this.
|
||||||
|
Eric Young for the C code this module was copied from.'''
|
||||||
|
|
||||||
|
__all__ = ['crypt']
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright (C) 2000, 2001 Carey Evans <careye@spamcop.net>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and distribute this software and
|
||||||
|
# its documentation for any purpose and without fee is hereby granted,
|
||||||
|
# provided that the above copyright notice appear in all copies and
|
||||||
|
# that both that copyright notice and this permission notice appear in
|
||||||
|
# supporting documentation.
|
||||||
|
#
|
||||||
|
# CAREY EVANS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||||
|
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
|
||||||
|
# EVENT SHALL CAREY EVANS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||||
|
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
|
||||||
|
# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
# Based on C code by Eric Young (eay@mincom.oz.au), which has the
|
||||||
|
# following copyright. Especially note condition 3, which imposes
|
||||||
|
# extra restrictions on top of the standard Python license used above.
|
||||||
|
#
|
||||||
|
# The fcrypt.c source is available from:
|
||||||
|
# ftp://ftp.psy.uq.oz.au/pub/Crypto/DES/
|
||||||
|
|
||||||
|
# ----- BEGIN fcrypt.c LICENSE -----
|
||||||
|
#
|
||||||
|
# This library is free for commercial and non-commercial use as long as
|
||||||
|
# the following conditions are aheared to. The following conditions
|
||||||
|
# apply to all code found in this distribution, be it the RC4, RSA,
|
||||||
|
# lhash, DES, etc., code; not just the SSL code. The SSL documentation
|
||||||
|
# included with this distribution is covered by the same copyright terms
|
||||||
|
# except that the holder is Tim Hudson (tjh@mincom.oz.au).
|
||||||
|
#
|
||||||
|
# Copyright remains Eric Young's, and as such any Copyright notices in
|
||||||
|
# the code are not to be removed.
|
||||||
|
# If this package is used in a product, Eric Young should be given attribution
|
||||||
|
# as the author of the parts of the library used.
|
||||||
|
# This can be in the form of a textual message at program startup or
|
||||||
|
# in documentation (online or textual) provided with the package.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# 3. All advertising materials mentioning features or use of this software
|
||||||
|
# must display the following acknowledgement:
|
||||||
|
# "This product includes cryptographic software written by
|
||||||
|
# Eric Young (eay@mincom.oz.au)"
|
||||||
|
# The word 'cryptographic' can be left out if the rouines from the library
|
||||||
|
# being used are not cryptographic related :-).
|
||||||
|
# 4. If you include any Windows specific code (or a derivative thereof) from
|
||||||
|
# the apps directory (application code) you must include an acknowledgement:
|
||||||
|
# "This product includes software written by Tim Hudson (tjh@mincom.oz.au)"
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# The licence and distribution terms for any publically available version or
|
||||||
|
# derivative of this code cannot be changed. i.e. this code cannot simply be
|
||||||
|
# copied and put under another distribution licence
|
||||||
|
# [including the GNU Public Licence.]
|
||||||
|
#
|
||||||
|
# ----- END fcrypt.c LICENSE -----
|
||||||
|
|
||||||
|
|
||||||
|
import string, struct
|
||||||
|
|
||||||
|
|
||||||
|
_ITERATIONS = 16
|
||||||
|
|
||||||
|
_SPtrans = (
|
||||||
|
# nibble 0
|
||||||
|
[ 0x00820200, 0x00020000, 0x80800000, 0x80820200,
|
||||||
|
0x00800000, 0x80020200, 0x80020000, 0x80800000,
|
||||||
|
0x80020200, 0x00820200, 0x00820000, 0x80000200,
|
||||||
|
0x80800200, 0x00800000, 0x00000000, 0x80020000,
|
||||||
|
0x00020000, 0x80000000, 0x00800200, 0x00020200,
|
||||||
|
0x80820200, 0x00820000, 0x80000200, 0x00800200,
|
||||||
|
0x80000000, 0x00000200, 0x00020200, 0x80820000,
|
||||||
|
0x00000200, 0x80800200, 0x80820000, 0x00000000,
|
||||||
|
0x00000000, 0x80820200, 0x00800200, 0x80020000,
|
||||||
|
0x00820200, 0x00020000, 0x80000200, 0x00800200,
|
||||||
|
0x80820000, 0x00000200, 0x00020200, 0x80800000,
|
||||||
|
0x80020200, 0x80000000, 0x80800000, 0x00820000,
|
||||||
|
0x80820200, 0x00020200, 0x00820000, 0x80800200,
|
||||||
|
0x00800000, 0x80000200, 0x80020000, 0x00000000,
|
||||||
|
0x00020000, 0x00800000, 0x80800200, 0x00820200,
|
||||||
|
0x80000000, 0x80820000, 0x00000200, 0x80020200 ],
|
||||||
|
|
||||||
|
# nibble 1
|
||||||
|
[ 0x10042004, 0x00000000, 0x00042000, 0x10040000,
|
||||||
|
0x10000004, 0x00002004, 0x10002000, 0x00042000,
|
||||||
|
0x00002000, 0x10040004, 0x00000004, 0x10002000,
|
||||||
|
0x00040004, 0x10042000, 0x10040000, 0x00000004,
|
||||||
|
0x00040000, 0x10002004, 0x10040004, 0x00002000,
|
||||||
|
0x00042004, 0x10000000, 0x00000000, 0x00040004,
|
||||||
|
0x10002004, 0x00042004, 0x10042000, 0x10000004,
|
||||||
|
0x10000000, 0x00040000, 0x00002004, 0x10042004,
|
||||||
|
0x00040004, 0x10042000, 0x10002000, 0x00042004,
|
||||||
|
0x10042004, 0x00040004, 0x10000004, 0x00000000,
|
||||||
|
0x10000000, 0x00002004, 0x00040000, 0x10040004,
|
||||||
|
0x00002000, 0x10000000, 0x00042004, 0x10002004,
|
||||||
|
0x10042000, 0x00002000, 0x00000000, 0x10000004,
|
||||||
|
0x00000004, 0x10042004, 0x00042000, 0x10040000,
|
||||||
|
0x10040004, 0x00040000, 0x00002004, 0x10002000,
|
||||||
|
0x10002004, 0x00000004, 0x10040000, 0x00042000 ],
|
||||||
|
|
||||||
|
# nibble 2
|
||||||
|
[ 0x41000000, 0x01010040, 0x00000040, 0x41000040,
|
||||||
|
0x40010000, 0x01000000, 0x41000040, 0x00010040,
|
||||||
|
0x01000040, 0x00010000, 0x01010000, 0x40000000,
|
||||||
|
0x41010040, 0x40000040, 0x40000000, 0x41010000,
|
||||||
|
0x00000000, 0x40010000, 0x01010040, 0x00000040,
|
||||||
|
0x40000040, 0x41010040, 0x00010000, 0x41000000,
|
||||||
|
0x41010000, 0x01000040, 0x40010040, 0x01010000,
|
||||||
|
0x00010040, 0x00000000, 0x01000000, 0x40010040,
|
||||||
|
0x01010040, 0x00000040, 0x40000000, 0x00010000,
|
||||||
|
0x40000040, 0x40010000, 0x01010000, 0x41000040,
|
||||||
|
0x00000000, 0x01010040, 0x00010040, 0x41010000,
|
||||||
|
0x40010000, 0x01000000, 0x41010040, 0x40000000,
|
||||||
|
0x40010040, 0x41000000, 0x01000000, 0x41010040,
|
||||||
|
0x00010000, 0x01000040, 0x41000040, 0x00010040,
|
||||||
|
0x01000040, 0x00000000, 0x41010000, 0x40000040,
|
||||||
|
0x41000000, 0x40010040, 0x00000040, 0x01010000 ],
|
||||||
|
|
||||||
|
# nibble 3
|
||||||
|
[ 0x00100402, 0x04000400, 0x00000002, 0x04100402,
|
||||||
|
0x00000000, 0x04100000, 0x04000402, 0x00100002,
|
||||||
|
0x04100400, 0x04000002, 0x04000000, 0x00000402,
|
||||||
|
0x04000002, 0x00100402, 0x00100000, 0x04000000,
|
||||||
|
0x04100002, 0x00100400, 0x00000400, 0x00000002,
|
||||||
|
0x00100400, 0x04000402, 0x04100000, 0x00000400,
|
||||||
|
0x00000402, 0x00000000, 0x00100002, 0x04100400,
|
||||||
|
0x04000400, 0x04100002, 0x04100402, 0x00100000,
|
||||||
|
0x04100002, 0x00000402, 0x00100000, 0x04000002,
|
||||||
|
0x00100400, 0x04000400, 0x00000002, 0x04100000,
|
||||||
|
0x04000402, 0x00000000, 0x00000400, 0x00100002,
|
||||||
|
0x00000000, 0x04100002, 0x04100400, 0x00000400,
|
||||||
|
0x04000000, 0x04100402, 0x00100402, 0x00100000,
|
||||||
|
0x04100402, 0x00000002, 0x04000400, 0x00100402,
|
||||||
|
0x00100002, 0x00100400, 0x04100000, 0x04000402,
|
||||||
|
0x00000402, 0x04000000, 0x04000002, 0x04100400 ],
|
||||||
|
|
||||||
|
# nibble 4
|
||||||
|
[ 0x02000000, 0x00004000, 0x00000100, 0x02004108,
|
||||||
|
0x02004008, 0x02000100, 0x00004108, 0x02004000,
|
||||||
|
0x00004000, 0x00000008, 0x02000008, 0x00004100,
|
||||||
|
0x02000108, 0x02004008, 0x02004100, 0x00000000,
|
||||||
|
0x00004100, 0x02000000, 0x00004008, 0x00000108,
|
||||||
|
0x02000100, 0x00004108, 0x00000000, 0x02000008,
|
||||||
|
0x00000008, 0x02000108, 0x02004108, 0x00004008,
|
||||||
|
0x02004000, 0x00000100, 0x00000108, 0x02004100,
|
||||||
|
0x02004100, 0x02000108, 0x00004008, 0x02004000,
|
||||||
|
0x00004000, 0x00000008, 0x02000008, 0x02000100,
|
||||||
|
0x02000000, 0x00004100, 0x02004108, 0x00000000,
|
||||||
|
0x00004108, 0x02000000, 0x00000100, 0x00004008,
|
||||||
|
0x02000108, 0x00000100, 0x00000000, 0x02004108,
|
||||||
|
0x02004008, 0x02004100, 0x00000108, 0x00004000,
|
||||||
|
0x00004100, 0x02004008, 0x02000100, 0x00000108,
|
||||||
|
0x00000008, 0x00004108, 0x02004000, 0x02000008 ],
|
||||||
|
|
||||||
|
# nibble 5
|
||||||
|
[ 0x20000010, 0x00080010, 0x00000000, 0x20080800,
|
||||||
|
0x00080010, 0x00000800, 0x20000810, 0x00080000,
|
||||||
|
0x00000810, 0x20080810, 0x00080800, 0x20000000,
|
||||||
|
0x20000800, 0x20000010, 0x20080000, 0x00080810,
|
||||||
|
0x00080000, 0x20000810, 0x20080010, 0x00000000,
|
||||||
|
0x00000800, 0x00000010, 0x20080800, 0x20080010,
|
||||||
|
0x20080810, 0x20080000, 0x20000000, 0x00000810,
|
||||||
|
0x00000010, 0x00080800, 0x00080810, 0x20000800,
|
||||||
|
0x00000810, 0x20000000, 0x20000800, 0x00080810,
|
||||||
|
0x20080800, 0x00080010, 0x00000000, 0x20000800,
|
||||||
|
0x20000000, 0x00000800, 0x20080010, 0x00080000,
|
||||||
|
0x00080010, 0x20080810, 0x00080800, 0x00000010,
|
||||||
|
0x20080810, 0x00080800, 0x00080000, 0x20000810,
|
||||||
|
0x20000010, 0x20080000, 0x00080810, 0x00000000,
|
||||||
|
0x00000800, 0x20000010, 0x20000810, 0x20080800,
|
||||||
|
0x20080000, 0x00000810, 0x00000010, 0x20080010 ],
|
||||||
|
|
||||||
|
# nibble 6
|
||||||
|
[ 0x00001000, 0x00000080, 0x00400080, 0x00400001,
|
||||||
|
0x00401081, 0x00001001, 0x00001080, 0x00000000,
|
||||||
|
0x00400000, 0x00400081, 0x00000081, 0x00401000,
|
||||||
|
0x00000001, 0x00401080, 0x00401000, 0x00000081,
|
||||||
|
0x00400081, 0x00001000, 0x00001001, 0x00401081,
|
||||||
|
0x00000000, 0x00400080, 0x00400001, 0x00001080,
|
||||||
|
0x00401001, 0x00001081, 0x00401080, 0x00000001,
|
||||||
|
0x00001081, 0x00401001, 0x00000080, 0x00400000,
|
||||||
|
0x00001081, 0x00401000, 0x00401001, 0x00000081,
|
||||||
|
0x00001000, 0x00000080, 0x00400000, 0x00401001,
|
||||||
|
0x00400081, 0x00001081, 0x00001080, 0x00000000,
|
||||||
|
0x00000080, 0x00400001, 0x00000001, 0x00400080,
|
||||||
|
0x00000000, 0x00400081, 0x00400080, 0x00001080,
|
||||||
|
0x00000081, 0x00001000, 0x00401081, 0x00400000,
|
||||||
|
0x00401080, 0x00000001, 0x00001001, 0x00401081,
|
||||||
|
0x00400001, 0x00401080, 0x00401000, 0x00001001 ],
|
||||||
|
|
||||||
|
# nibble 7
|
||||||
|
[ 0x08200020, 0x08208000, 0x00008020, 0x00000000,
|
||||||
|
0x08008000, 0x00200020, 0x08200000, 0x08208020,
|
||||||
|
0x00000020, 0x08000000, 0x00208000, 0x00008020,
|
||||||
|
0x00208020, 0x08008020, 0x08000020, 0x08200000,
|
||||||
|
0x00008000, 0x00208020, 0x00200020, 0x08008000,
|
||||||
|
0x08208020, 0x08000020, 0x00000000, 0x00208000,
|
||||||
|
0x08000000, 0x00200000, 0x08008020, 0x08200020,
|
||||||
|
0x00200000, 0x00008000, 0x08208000, 0x00000020,
|
||||||
|
0x00200000, 0x00008000, 0x08000020, 0x08208020,
|
||||||
|
0x00008020, 0x08000000, 0x00000000, 0x00208000,
|
||||||
|
0x08200020, 0x08008020, 0x08008000, 0x00200020,
|
||||||
|
0x08208000, 0x00000020, 0x00200020, 0x08008000,
|
||||||
|
0x08208020, 0x00200000, 0x08200000, 0x08000020,
|
||||||
|
0x00208000, 0x00008020, 0x08008020, 0x08200000,
|
||||||
|
0x00000020, 0x08208000, 0x00208020, 0x00000000,
|
||||||
|
0x08000000, 0x08200020, 0x00008000, 0x00208020 ] )
|
||||||
|
|
||||||
|
_skb = (
|
||||||
|
# for C bits (numbered as per FIPS 46) 1 2 3 4 5 6
|
||||||
|
[ 0x00000000, 0x00000010, 0x20000000, 0x20000010,
|
||||||
|
0x00010000, 0x00010010, 0x20010000, 0x20010010,
|
||||||
|
0x00000800, 0x00000810, 0x20000800, 0x20000810,
|
||||||
|
0x00010800, 0x00010810, 0x20010800, 0x20010810,
|
||||||
|
0x00000020, 0x00000030, 0x20000020, 0x20000030,
|
||||||
|
0x00010020, 0x00010030, 0x20010020, 0x20010030,
|
||||||
|
0x00000820, 0x00000830, 0x20000820, 0x20000830,
|
||||||
|
0x00010820, 0x00010830, 0x20010820, 0x20010830,
|
||||||
|
0x00080000, 0x00080010, 0x20080000, 0x20080010,
|
||||||
|
0x00090000, 0x00090010, 0x20090000, 0x20090010,
|
||||||
|
0x00080800, 0x00080810, 0x20080800, 0x20080810,
|
||||||
|
0x00090800, 0x00090810, 0x20090800, 0x20090810,
|
||||||
|
0x00080020, 0x00080030, 0x20080020, 0x20080030,
|
||||||
|
0x00090020, 0x00090030, 0x20090020, 0x20090030,
|
||||||
|
0x00080820, 0x00080830, 0x20080820, 0x20080830,
|
||||||
|
0x00090820, 0x00090830, 0x20090820, 0x20090830 ],
|
||||||
|
|
||||||
|
# for C bits (numbered as per FIPS 46) 7 8 10 11 12 13
|
||||||
|
[ 0x00000000, 0x02000000, 0x00002000, 0x02002000,
|
||||||
|
0x00200000, 0x02200000, 0x00202000, 0x02202000,
|
||||||
|
0x00000004, 0x02000004, 0x00002004, 0x02002004,
|
||||||
|
0x00200004, 0x02200004, 0x00202004, 0x02202004,
|
||||||
|
0x00000400, 0x02000400, 0x00002400, 0x02002400,
|
||||||
|
0x00200400, 0x02200400, 0x00202400, 0x02202400,
|
||||||
|
0x00000404, 0x02000404, 0x00002404, 0x02002404,
|
||||||
|
0x00200404, 0x02200404, 0x00202404, 0x02202404,
|
||||||
|
0x10000000, 0x12000000, 0x10002000, 0x12002000,
|
||||||
|
0x10200000, 0x12200000, 0x10202000, 0x12202000,
|
||||||
|
0x10000004, 0x12000004, 0x10002004, 0x12002004,
|
||||||
|
0x10200004, 0x12200004, 0x10202004, 0x12202004,
|
||||||
|
0x10000400, 0x12000400, 0x10002400, 0x12002400,
|
||||||
|
0x10200400, 0x12200400, 0x10202400, 0x12202400,
|
||||||
|
0x10000404, 0x12000404, 0x10002404, 0x12002404,
|
||||||
|
0x10200404, 0x12200404, 0x10202404, 0x12202404 ],
|
||||||
|
|
||||||
|
# for C bits (numbered as per FIPS 46) 14 15 16 17 19 20
|
||||||
|
[ 0x00000000, 0x00000001, 0x00040000, 0x00040001,
|
||||||
|
0x01000000, 0x01000001, 0x01040000, 0x01040001,
|
||||||
|
0x00000002, 0x00000003, 0x00040002, 0x00040003,
|
||||||
|
0x01000002, 0x01000003, 0x01040002, 0x01040003,
|
||||||
|
0x00000200, 0x00000201, 0x00040200, 0x00040201,
|
||||||
|
0x01000200, 0x01000201, 0x01040200, 0x01040201,
|
||||||
|
0x00000202, 0x00000203, 0x00040202, 0x00040203,
|
||||||
|
0x01000202, 0x01000203, 0x01040202, 0x01040203,
|
||||||
|
0x08000000, 0x08000001, 0x08040000, 0x08040001,
|
||||||
|
0x09000000, 0x09000001, 0x09040000, 0x09040001,
|
||||||
|
0x08000002, 0x08000003, 0x08040002, 0x08040003,
|
||||||
|
0x09000002, 0x09000003, 0x09040002, 0x09040003,
|
||||||
|
0x08000200, 0x08000201, 0x08040200, 0x08040201,
|
||||||
|
0x09000200, 0x09000201, 0x09040200, 0x09040201,
|
||||||
|
0x08000202, 0x08000203, 0x08040202, 0x08040203,
|
||||||
|
0x09000202, 0x09000203, 0x09040202, 0x09040203 ],
|
||||||
|
|
||||||
|
# for C bits (numbered as per FIPS 46) 21 23 24 26 27 28
|
||||||
|
[ 0x00000000, 0x00100000, 0x00000100, 0x00100100,
|
||||||
|
0x00000008, 0x00100008, 0x00000108, 0x00100108,
|
||||||
|
0x00001000, 0x00101000, 0x00001100, 0x00101100,
|
||||||
|
0x00001008, 0x00101008, 0x00001108, 0x00101108,
|
||||||
|
0x04000000, 0x04100000, 0x04000100, 0x04100100,
|
||||||
|
0x04000008, 0x04100008, 0x04000108, 0x04100108,
|
||||||
|
0x04001000, 0x04101000, 0x04001100, 0x04101100,
|
||||||
|
0x04001008, 0x04101008, 0x04001108, 0x04101108,
|
||||||
|
0x00020000, 0x00120000, 0x00020100, 0x00120100,
|
||||||
|
0x00020008, 0x00120008, 0x00020108, 0x00120108,
|
||||||
|
0x00021000, 0x00121000, 0x00021100, 0x00121100,
|
||||||
|
0x00021008, 0x00121008, 0x00021108, 0x00121108,
|
||||||
|
0x04020000, 0x04120000, 0x04020100, 0x04120100,
|
||||||
|
0x04020008, 0x04120008, 0x04020108, 0x04120108,
|
||||||
|
0x04021000, 0x04121000, 0x04021100, 0x04121100,
|
||||||
|
0x04021008, 0x04121008, 0x04021108, 0x04121108 ],
|
||||||
|
|
||||||
|
# for D bits (numbered as per FIPS 46) 1 2 3 4 5 6
|
||||||
|
[ 0x00000000, 0x10000000, 0x00010000, 0x10010000,
|
||||||
|
0x00000004, 0x10000004, 0x00010004, 0x10010004,
|
||||||
|
0x20000000, 0x30000000, 0x20010000, 0x30010000,
|
||||||
|
0x20000004, 0x30000004, 0x20010004, 0x30010004,
|
||||||
|
0x00100000, 0x10100000, 0x00110000, 0x10110000,
|
||||||
|
0x00100004, 0x10100004, 0x00110004, 0x10110004,
|
||||||
|
0x20100000, 0x30100000, 0x20110000, 0x30110000,
|
||||||
|
0x20100004, 0x30100004, 0x20110004, 0x30110004,
|
||||||
|
0x00001000, 0x10001000, 0x00011000, 0x10011000,
|
||||||
|
0x00001004, 0x10001004, 0x00011004, 0x10011004,
|
||||||
|
0x20001000, 0x30001000, 0x20011000, 0x30011000,
|
||||||
|
0x20001004, 0x30001004, 0x20011004, 0x30011004,
|
||||||
|
0x00101000, 0x10101000, 0x00111000, 0x10111000,
|
||||||
|
0x00101004, 0x10101004, 0x00111004, 0x10111004,
|
||||||
|
0x20101000, 0x30101000, 0x20111000, 0x30111000,
|
||||||
|
0x20101004, 0x30101004, 0x20111004, 0x30111004 ],
|
||||||
|
|
||||||
|
# for D bits (numbered as per FIPS 46) 8 9 11 12 13 14
|
||||||
|
[ 0x00000000, 0x08000000, 0x00000008, 0x08000008,
|
||||||
|
0x00000400, 0x08000400, 0x00000408, 0x08000408,
|
||||||
|
0x00020000, 0x08020000, 0x00020008, 0x08020008,
|
||||||
|
0x00020400, 0x08020400, 0x00020408, 0x08020408,
|
||||||
|
0x00000001, 0x08000001, 0x00000009, 0x08000009,
|
||||||
|
0x00000401, 0x08000401, 0x00000409, 0x08000409,
|
||||||
|
0x00020001, 0x08020001, 0x00020009, 0x08020009,
|
||||||
|
0x00020401, 0x08020401, 0x00020409, 0x08020409,
|
||||||
|
0x02000000, 0x0A000000, 0x02000008, 0x0A000008,
|
||||||
|
0x02000400, 0x0A000400, 0x02000408, 0x0A000408,
|
||||||
|
0x02020000, 0x0A020000, 0x02020008, 0x0A020008,
|
||||||
|
0x02020400, 0x0A020400, 0x02020408, 0x0A020408,
|
||||||
|
0x02000001, 0x0A000001, 0x02000009, 0x0A000009,
|
||||||
|
0x02000401, 0x0A000401, 0x02000409, 0x0A000409,
|
||||||
|
0x02020001, 0x0A020001, 0x02020009, 0x0A020009,
|
||||||
|
0x02020401, 0x0A020401, 0x02020409, 0x0A020409 ],
|
||||||
|
|
||||||
|
# for D bits (numbered as per FIPS 46) 16 17 18 19 20 21
|
||||||
|
[ 0x00000000, 0x00000100, 0x00080000, 0x00080100,
|
||||||
|
0x01000000, 0x01000100, 0x01080000, 0x01080100,
|
||||||
|
0x00000010, 0x00000110, 0x00080010, 0x00080110,
|
||||||
|
0x01000010, 0x01000110, 0x01080010, 0x01080110,
|
||||||
|
0x00200000, 0x00200100, 0x00280000, 0x00280100,
|
||||||
|
0x01200000, 0x01200100, 0x01280000, 0x01280100,
|
||||||
|
0x00200010, 0x00200110, 0x00280010, 0x00280110,
|
||||||
|
0x01200010, 0x01200110, 0x01280010, 0x01280110,
|
||||||
|
0x00000200, 0x00000300, 0x00080200, 0x00080300,
|
||||||
|
0x01000200, 0x01000300, 0x01080200, 0x01080300,
|
||||||
|
0x00000210, 0x00000310, 0x00080210, 0x00080310,
|
||||||
|
0x01000210, 0x01000310, 0x01080210, 0x01080310,
|
||||||
|
0x00200200, 0x00200300, 0x00280200, 0x00280300,
|
||||||
|
0x01200200, 0x01200300, 0x01280200, 0x01280300,
|
||||||
|
0x00200210, 0x00200310, 0x00280210, 0x00280310,
|
||||||
|
0x01200210, 0x01200310, 0x01280210, 0x01280310 ],
|
||||||
|
|
||||||
|
# for D bits (numbered as per FIPS 46) 22 23 24 25 27 28
|
||||||
|
[ 0x00000000, 0x04000000, 0x00040000, 0x04040000,
|
||||||
|
0x00000002, 0x04000002, 0x00040002, 0x04040002,
|
||||||
|
0x00002000, 0x04002000, 0x00042000, 0x04042000,
|
||||||
|
0x00002002, 0x04002002, 0x00042002, 0x04042002,
|
||||||
|
0x00000020, 0x04000020, 0x00040020, 0x04040020,
|
||||||
|
0x00000022, 0x04000022, 0x00040022, 0x04040022,
|
||||||
|
0x00002020, 0x04002020, 0x00042020, 0x04042020,
|
||||||
|
0x00002022, 0x04002022, 0x00042022, 0x04042022,
|
||||||
|
0x00000800, 0x04000800, 0x00040800, 0x04040800,
|
||||||
|
0x00000802, 0x04000802, 0x00040802, 0x04040802,
|
||||||
|
0x00002800, 0x04002800, 0x00042800, 0x04042800,
|
||||||
|
0x00002802, 0x04002802, 0x00042802, 0x04042802,
|
||||||
|
0x00000820, 0x04000820, 0x00040820, 0x04040820,
|
||||||
|
0x00000822, 0x04000822, 0x00040822, 0x04040822,
|
||||||
|
0x00002820, 0x04002820, 0x00042820, 0x04042820,
|
||||||
|
0x00002822, 0x04002822, 0x00042822, 0x04042822 ] )
|
||||||
|
|
||||||
|
_shifts2 = (0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0)
|
||||||
|
|
||||||
|
_con_salt = [
|
||||||
|
0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,
|
||||||
|
0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,0xE0,0xE1,
|
||||||
|
0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,
|
||||||
|
0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,
|
||||||
|
0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,
|
||||||
|
0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,0x00,0x01,
|
||||||
|
0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
|
||||||
|
0x0A,0x0B,0x05,0x06,0x07,0x08,0x09,0x0A,
|
||||||
|
0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,
|
||||||
|
0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,
|
||||||
|
0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,
|
||||||
|
0x23,0x24,0x25,0x20,0x21,0x22,0x23,0x24,
|
||||||
|
0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,
|
||||||
|
0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,
|
||||||
|
0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,
|
||||||
|
0x3D,0x3E,0x3F,0x40,0x41,0x42,0x43,0x44 ]
|
||||||
|
|
||||||
|
_cov_2char = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||||
|
|
||||||
|
|
||||||
|
def _HPERM_OP(a):
|
||||||
|
"""Clever bit manipulation."""
|
||||||
|
t = ((a << 18) ^ a) & 0xcccc0000
|
||||||
|
return a ^ t ^ ((t >> 18) & 0x3fff)
|
||||||
|
|
||||||
|
def _PERM_OP(a,b,n,m):
|
||||||
|
"""Cleverer bit manipulation."""
|
||||||
|
t = ((a >> n) ^ b) & m
|
||||||
|
b = b ^ t
|
||||||
|
a = a ^ (t << n)
|
||||||
|
return a,b
|
||||||
|
|
||||||
|
|
||||||
|
def _set_key(password):
|
||||||
|
"""Generate DES key schedule from ASCII password."""
|
||||||
|
|
||||||
|
c,d = struct.unpack('<ii', password)
|
||||||
|
c = (c & 0x7f7f7f7f) << 1
|
||||||
|
d = (d & 0x7f7f7f7f) << 1
|
||||||
|
|
||||||
|
d,c = _PERM_OP(d,c,4,0x0f0f0f0f)
|
||||||
|
c = _HPERM_OP(c)
|
||||||
|
d = _HPERM_OP(d)
|
||||||
|
d,c = _PERM_OP(d,c,1,0x55555555)
|
||||||
|
c,d = _PERM_OP(c,d,8,0x00ff00ff)
|
||||||
|
d,c = _PERM_OP(d,c,1,0x55555555)
|
||||||
|
|
||||||
|
# Any sign-extended bits are masked off.
|
||||||
|
d = (((d & 0x000000ff) << 16) | (d & 0x0000ff00) |
|
||||||
|
((d & 0x00ff0000) >> 16) | ((c >> 4) & 0x0f000000))
|
||||||
|
c = c & 0x0fffffff
|
||||||
|
|
||||||
|
# Copy globals into local variables for loop.
|
||||||
|
shifts2 = _shifts2
|
||||||
|
skbc0, skbc1, skbc2, skbc3, skbd0, skbd1, skbd2, skbd3 = _skb
|
||||||
|
|
||||||
|
k = [0] * (_ITERATIONS * 2)
|
||||||
|
|
||||||
|
for i in range(_ITERATIONS):
|
||||||
|
# Only operates on top 28 bits.
|
||||||
|
if shifts2[i]:
|
||||||
|
c = (c >> 2) | (c << 26)
|
||||||
|
d = (d >> 2) | (d << 26)
|
||||||
|
else:
|
||||||
|
c = (c >> 1) | (c << 27)
|
||||||
|
d = (d >> 1) | (d << 27)
|
||||||
|
c = c & 0x0fffffff
|
||||||
|
d = d & 0x0fffffff
|
||||||
|
|
||||||
|
s = ( skbc0[ c & 0x3f ] |
|
||||||
|
skbc1[((c>> 6) & 0x03) | ((c>> 7) & 0x3c)] |
|
||||||
|
skbc2[((c>>13) & 0x0f) | ((c>>14) & 0x30)] |
|
||||||
|
skbc3[((c>>20) & 0x01) |
|
||||||
|
((c>>21) & 0x06) | ((c>>22) & 0x38)] )
|
||||||
|
|
||||||
|
t = ( skbd0[ d & 0x3f ] |
|
||||||
|
skbd1[((d>> 7) & 0x03) | ((d>> 8) & 0x3c)] |
|
||||||
|
skbd2[((d>>15) & 0x3f) ] |
|
||||||
|
skbd3[((d>>21) & 0x0f) | ((d>>22) & 0x30)] )
|
||||||
|
|
||||||
|
k[2*i] = ((t << 16) | (s & 0x0000ffff)) & 0xffffffff
|
||||||
|
s = (s >> 16) | (t & 0xffff0000)
|
||||||
|
|
||||||
|
# Top bit of s may be 1.
|
||||||
|
s = (s << 4) | ((s >> 28) & 0x0f)
|
||||||
|
k[2*i + 1] = s & 0xffffffff
|
||||||
|
|
||||||
|
return k
|
||||||
|
|
||||||
|
|
||||||
|
def _body(ks, E0, E1):
|
||||||
|
"""Use the key schedule ks and salt E0, E1 to create the password hash."""
|
||||||
|
|
||||||
|
# Copy global variable into locals for loop.
|
||||||
|
SP0, SP1, SP2, SP3, SP4, SP5, SP6, SP7 = _SPtrans
|
||||||
|
|
||||||
|
inner = range(0, _ITERATIONS*2, 2)
|
||||||
|
l = r = 0
|
||||||
|
for j in range(25):
|
||||||
|
l,r = r,l
|
||||||
|
for i in inner:
|
||||||
|
t = r ^ ((r >> 16) & 0xffff)
|
||||||
|
u = t & E0
|
||||||
|
t = t & E1
|
||||||
|
u = u ^ (u << 16) ^ r ^ ks[i]
|
||||||
|
t = t ^ (t << 16) ^ r ^ ks[i+1]
|
||||||
|
t = ((t >> 4) & 0x0fffffff) | (t << 28)
|
||||||
|
|
||||||
|
l,r = r,(SP1[(t ) & 0x3f] ^ SP3[(t>> 8) & 0x3f] ^
|
||||||
|
SP5[(t>>16) & 0x3f] ^ SP7[(t>>24) & 0x3f] ^
|
||||||
|
SP0[(u ) & 0x3f] ^ SP2[(u>> 8) & 0x3f] ^
|
||||||
|
SP4[(u>>16) & 0x3f] ^ SP6[(u>>24) & 0x3f] ^ l)
|
||||||
|
|
||||||
|
l = ((l >> 1) & 0x7fffffff) | ((l & 0x1) << 31)
|
||||||
|
r = ((r >> 1) & 0x7fffffff) | ((r & 0x1) << 31)
|
||||||
|
|
||||||
|
r,l = _PERM_OP(r, l, 1, 0x55555555)
|
||||||
|
l,r = _PERM_OP(l, r, 8, 0x00ff00ff)
|
||||||
|
r,l = _PERM_OP(r, l, 2, 0x33333333)
|
||||||
|
l,r = _PERM_OP(l, r, 16, 0x0000ffff)
|
||||||
|
r,l = _PERM_OP(r, l, 4, 0x0f0f0f0f)
|
||||||
|
|
||||||
|
return l,r
|
||||||
|
|
||||||
|
|
||||||
|
def crypt(password, salt):
|
||||||
|
"""Generate an encrypted hash from the passed password. If the password
|
||||||
|
is longer than eight characters, only the first eight will be used.
|
||||||
|
|
||||||
|
The first two characters of the salt are used to modify the encryption
|
||||||
|
algorithm used to generate in the hash in one of 4096 different ways.
|
||||||
|
The characters for the salt must be alphanumeric, '.' or '/'.
|
||||||
|
|
||||||
|
The returned hash begins with the two characters of the salt, and
|
||||||
|
should be passed as the salt to verify the password.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
>>> from fcrypt import crypt
|
||||||
|
>>> password = 'AlOtBsOl'
|
||||||
|
>>> salt = 'cE'
|
||||||
|
>>> hash = crypt(password, salt)
|
||||||
|
>>> hash
|
||||||
|
'cEpWz5IUCShqM'
|
||||||
|
>>> crypt(password, hash) == hash
|
||||||
|
1
|
||||||
|
>>> crypt('IaLaIoK', hash) == hash
|
||||||
|
0
|
||||||
|
|
||||||
|
In practice, you would read the password using something like the
|
||||||
|
getpass module, and generate the salt randomly:
|
||||||
|
|
||||||
|
>>> import random, string
|
||||||
|
>>> saltchars = string.letters + string.digits + './'
|
||||||
|
>>> salt = random.choice(saltchars) + random.choice(saltchars)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(salt) < 2:
|
||||||
|
salt = salt + 'AA'
|
||||||
|
|
||||||
|
Eswap0 = _con_salt[ord(salt[0])]
|
||||||
|
Eswap1 = _con_salt[ord(salt[1])] << 4
|
||||||
|
|
||||||
|
ks = _set_key((password + '\0\0\0\0\0\0\0\0')[:8])
|
||||||
|
out1,out2 = _body(ks, Eswap0, Eswap1)
|
||||||
|
|
||||||
|
# Convert numbers to big-endian...
|
||||||
|
be1, be2 = struct.unpack('>ii', struct.pack('<ii', out1, out2))
|
||||||
|
# then extract 24-bit subsets.
|
||||||
|
b24 = [(be1 >> 8) & 0xffffff,
|
||||||
|
((be1 << 16) & 0xff0000) | ((be2 >> 16) & 0xffff),
|
||||||
|
(be2 << 8) & 0xffff00]
|
||||||
|
|
||||||
|
# Convert to ASCII encoding, 4 characters for each 24 bits.
|
||||||
|
res = [salt[0], salt[1]]
|
||||||
|
for b in b24:
|
||||||
|
for i in range(18, -6, -6):
|
||||||
|
res.append(_cov_2char[(b >> i) & 0x3f])
|
||||||
|
|
||||||
|
return string.join(res[:13], '')
|
||||||
|
|
||||||
|
def _test():
|
||||||
|
"""Run doctest on fcrypt module."""
|
||||||
|
import doctest, fcrypt
|
||||||
|
return doctest.testmod(fcrypt)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
_test()
|
22
ZopeProducts/exUserFolder/CryptoSources/fcrypt/setup.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# distutils setup script for fcrypt.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2000, 2001 Carey Evans <careye@spamcop.net>
|
||||||
|
|
||||||
|
from distutils.core import setup
|
||||||
|
|
||||||
|
setup( name = 'fcrypt',
|
||||||
|
version = '1.2',
|
||||||
|
description = 'The Unix password crypt function.',
|
||||||
|
author = 'Carey Evans',
|
||||||
|
author_email = 'careye@spamcop.net',
|
||||||
|
url = 'http://home.clear.net.nz/pages/c.evans/sw/',
|
||||||
|
licence = 'BSD',
|
||||||
|
long_description = """\
|
||||||
|
A pure Python implementation of the Unix DES password crypt function,
|
||||||
|
based on Eric Young's fcrypt.c. It works with any version of Python
|
||||||
|
from version 1.5 or higher, and because it's pure Python it doesn't
|
||||||
|
need a C compiler to install it.""",
|
||||||
|
|
||||||
|
py_modules = ['fcrypt'] )
|
44
ZopeProducts/exUserFolder/CryptoSources/pass_crypt.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: pass_crypt.py,v 1.3 2004/11/18 09:24:46 akm Exp $
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import CryptoPluginRegister
|
||||||
|
|
||||||
|
try:
|
||||||
|
from crypt import crypt
|
||||||
|
except:
|
||||||
|
from fcrypt.fcrypt import crypt
|
||||||
|
|
||||||
|
|
||||||
|
def cryptPassword(authSource, username, password):
|
||||||
|
u = authSource.listOneUser(username)
|
||||||
|
if not u:
|
||||||
|
salt = username[:2]
|
||||||
|
else:
|
||||||
|
salt=u[0]['password'][:2]
|
||||||
|
|
||||||
|
secret = crypt(password, salt)
|
||||||
|
return secret
|
||||||
|
|
||||||
|
|
||||||
|
CryptPlugin=CryptoPluginRegister('Crypt', 'crypt', 'Crypt', cryptPassword)
|
||||||
|
exUserFolder.cryptoSources['Crypt']=CryptPlugin
|
47
ZopeProducts/exUserFolder/CryptoSources/pass_md5.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: pass_md5.py,v 1.1 2004/11/10 14:15:52 akm Exp $
|
||||||
|
|
||||||
|
import md5, base64, string
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import CryptoPluginRegister
|
||||||
|
|
||||||
|
# Simple digest
|
||||||
|
def cryptPassword(authSource, username, password):
|
||||||
|
digest = md5.new()
|
||||||
|
digest.update(password)
|
||||||
|
digest = digest.digest()
|
||||||
|
secret = string.strip(base64.encodestring(digest))
|
||||||
|
return secret
|
||||||
|
|
||||||
|
# Digest includes username
|
||||||
|
# So two passwords for different users hash differently
|
||||||
|
def cryptPassword2(authSource, username, password):
|
||||||
|
newPass = username+':'+password
|
||||||
|
return cryptPassword(authSource, username, newPass)
|
||||||
|
|
||||||
|
|
||||||
|
MD5Plugin1=CryptoPluginRegister('MD51', 'MD5', 'MD5 Password Only', cryptPassword)
|
||||||
|
exUserFolder.cryptoSources['MD51']=MD5Plugin1
|
||||||
|
|
||||||
|
MD5Plugin2=CryptoPluginRegister('MD52', 'MD5', 'MD5 Username + Password', cryptPassword2)
|
||||||
|
exUserFolder.cryptoSources['MD52']=MD5Plugin2
|
31
ZopeProducts/exUserFolder/CryptoSources/pass_plain.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: pass_plain.py,v 1.1 2004/11/10 14:15:52 akm Exp $
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import CryptoPluginRegister
|
||||||
|
|
||||||
|
# Simple digest
|
||||||
|
def cryptPassword(authSource, username, password):
|
||||||
|
return password
|
||||||
|
|
||||||
|
PlainPlugin=CryptoPluginRegister('Plaintext', 'Plaintext', 'No Encryption', cryptPassword)
|
||||||
|
exUserFolder.cryptoSources['Plaintext']=PlainPlugin
|
41
ZopeProducts/exUserFolder/CryptoSources/pass_sha.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: pass_sha.py,v 1.1 2004/11/10 14:15:52 akm Exp $
|
||||||
|
|
||||||
|
import sha
|
||||||
|
from base64 import encodestring
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import CryptoPluginRegister
|
||||||
|
|
||||||
|
|
||||||
|
def cryptPassword(authSource, username, password):
|
||||||
|
return encodestring(sha.new(password).digest())
|
||||||
|
|
||||||
|
def cryptPassword2(authSource, username, password):
|
||||||
|
newPass = username+':'+password
|
||||||
|
return cryptPassword(authSource, username, newPass)
|
||||||
|
|
||||||
|
SHAPlugin1=CryptoPluginRegister('SHA1', 'SHA', 'SHA Password Only', cryptPassword)
|
||||||
|
exUserFolder.cryptoSources['SHA1']=SHAPlugin1
|
||||||
|
|
||||||
|
SHAPlugin2=CryptoPluginRegister('SHA2', 'SHA', 'SHA Username + Password', cryptPassword2)
|
||||||
|
exUserFolder.cryptoSources['SHA2']=SHAPlugin2
|
4
ZopeProducts/exUserFolder/Extensions/.cvsignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
20
ZopeProducts/exUserFolder/Extensions/getOldGroups.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# This script interrogates the old-skool NuxUserGroups_support_branch
|
||||||
|
# group structure and outputs a tab-delimited file you can send to
|
||||||
|
# loadOldGroups. Just in case anyone is using it. :-)
|
||||||
|
#
|
||||||
|
# Matt Behrens <matt.behrens@kohler.com>
|
||||||
|
|
||||||
|
def getOldGroups(self):
|
||||||
|
"Reconstruct a group list from the old-style _groups property"
|
||||||
|
from string import join
|
||||||
|
props = self.currentPropSource.userProperties
|
||||||
|
groups = {}
|
||||||
|
for username in props.keys():
|
||||||
|
for groupname in props[username].getProperty('_groups', ()):
|
||||||
|
if not groups.has_key(groupname):
|
||||||
|
groups[groupname] = []
|
||||||
|
groups[groupname].append(username)
|
||||||
|
out = ''
|
||||||
|
for groupname in groups.keys():
|
||||||
|
out = out + '%s %s\n' % (groupname, join(groups[groupname], ' '))
|
||||||
|
return out
|
26
ZopeProducts/exUserFolder/Extensions/loadOldGroups.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# This takes 'old_groups.txt' from var (create it using getOldGroups)
|
||||||
|
# and sets up all the groups therein using NuxUserGroups calls. This
|
||||||
|
# will load a group source if you need to do such a thing.
|
||||||
|
#
|
||||||
|
# Matt Behrens <matt.behrens@kohler.com>
|
||||||
|
|
||||||
|
def loadOldGroups(self):
|
||||||
|
from os.path import join as pathJoin
|
||||||
|
from string import split, strip
|
||||||
|
|
||||||
|
groups_file = open(pathJoin(CLIENT_HOME, 'old_groups.txt'), 'r')
|
||||||
|
out = ''
|
||||||
|
for group_line in groups_file.readlines():
|
||||||
|
group_line_elements = split(strip(group_line), ' ')
|
||||||
|
group_name = group_line_elements[0]
|
||||||
|
group_members = group_line_elements[1:]
|
||||||
|
|
||||||
|
if self.getGroupById(group_name, default=None) is None:
|
||||||
|
out = out + 'adding group %s\n' % group_name
|
||||||
|
self.userFolderAddGroup(group_name)
|
||||||
|
|
||||||
|
out = out + 'setting group %s membership to %s\n' % (group_name, group_members)
|
||||||
|
self.setUsersOfGroup(group_members, group_name)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
140
ZopeProducts/exUserFolder/Extensions/usAuthSourceMethods.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: usAuthSourceMethods.py,v 1.3 2001/12/01 08:40:04 akm Exp $
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# This is an example of an Extension Module to provide User Supplied
|
||||||
|
# Authentication Methods.
|
||||||
|
#
|
||||||
|
# It mimics the behaviour of the pgAuthSource Module, and the sql queries
|
||||||
|
# Used here would be added as ZSQLMethods in the usAuthSource Folder.
|
||||||
|
# (you can basically cut and paste them from the bottom of this .py file
|
||||||
|
# into the ZSQL Method Template Area
|
||||||
|
#
|
||||||
|
# It's not complete, but, you do get the idea...
|
||||||
|
#
|
||||||
|
# Each function becomes usFunctionName
|
||||||
|
#
|
||||||
|
# e.g. listOneUser -> usListOneUser
|
||||||
|
#
|
||||||
|
import string
|
||||||
|
from crypt import crypt
|
||||||
|
|
||||||
|
def listOneUser(self,username):
|
||||||
|
users = []
|
||||||
|
result=self.sqlListOneUser(username=username)
|
||||||
|
for n in result:
|
||||||
|
username=sqlattr(n,'username')
|
||||||
|
password=sqlattr(n,'password')
|
||||||
|
roles=string.split(sqlattr(n,'roles'))
|
||||||
|
N={'username':username, 'password':password, 'roles':roles}
|
||||||
|
users.append(N)
|
||||||
|
return users
|
||||||
|
|
||||||
|
def listUsers(self):
|
||||||
|
"""Returns a list of user names or [] if no users exist"""
|
||||||
|
users = []
|
||||||
|
result=self.sqlListUsers()
|
||||||
|
for n in result:
|
||||||
|
username=sqlattr(n,'username')
|
||||||
|
N={'username':username}
|
||||||
|
users.append(N)
|
||||||
|
return users
|
||||||
|
|
||||||
|
def getUsers(self):
|
||||||
|
"""Return a list of user objects or [] if no users exist"""
|
||||||
|
data=[]
|
||||||
|
try: items=self.listusers()
|
||||||
|
except: return data
|
||||||
|
for people in items:
|
||||||
|
roles=string.split(people['roles'],',')
|
||||||
|
user=User(people['username'], roles, '')
|
||||||
|
data.append(user)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def cryptPassword(self, username, password):
|
||||||
|
salt =username[:2]
|
||||||
|
secret = crypt(password, salt)
|
||||||
|
return secret
|
||||||
|
|
||||||
|
def deleteUsers(self, userids):
|
||||||
|
for uid in userids:
|
||||||
|
self.sqlDeleteOneUser(userid=uid)
|
||||||
|
|
||||||
|
|
||||||
|
# Helper Functions...
|
||||||
|
from string import upper, lower
|
||||||
|
import Missing
|
||||||
|
mt=type(Missing.Value)
|
||||||
|
|
||||||
|
def typeconv(val):
|
||||||
|
if type(val)==mt:
|
||||||
|
return ''
|
||||||
|
return val
|
||||||
|
|
||||||
|
def sqlattr(ob, attr):
|
||||||
|
name=attr
|
||||||
|
if hasattr(ob, attr):
|
||||||
|
return typeconv(getattr(ob, attr))
|
||||||
|
attr=upper(attr)
|
||||||
|
if hasattr(ob, attr):
|
||||||
|
return typeconv(getattr(ob, attr))
|
||||||
|
attr=lower(attr)
|
||||||
|
if hasattr(ob, attr):
|
||||||
|
return typeconv(getattr(ob, attr))
|
||||||
|
raise NameError, name
|
||||||
|
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# SQL METHODS USED ABOVE
|
||||||
|
# PASTE INTO ZSQL METHODS
|
||||||
|
# take note of what parameters are used in each query
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
_sqlListUsers="""
|
||||||
|
SELECT * FROM passwd
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlListOneUser="""
|
||||||
|
SELECT * FROM passwd
|
||||||
|
where username=<dtml-sqlvar username type=string>
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlDeleteOneUser="""
|
||||||
|
DELETE FROM passwd
|
||||||
|
where uid=<dtml-sqlvar userid type=int>
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlInsertUser="""
|
||||||
|
INSERT INTO passwd (username, password, roles)
|
||||||
|
VALUES (<dtml-sqlvar username type=string>,
|
||||||
|
<dtml-sqlvar password type=string>,
|
||||||
|
<dtml-sqlvar roles type=string>)
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlUpdateUserPassword="""
|
||||||
|
UPDATE passwd set password=<dtml-sqlvar password type=string>
|
||||||
|
WHERE username=<dtml-sqlvar username type=string>
|
||||||
|
"""
|
||||||
|
|
||||||
|
_sqlUpdateUser="""
|
||||||
|
UPDATE passwd set roles=<dtml-sqlvar roles type=string>
|
||||||
|
WHERE username=<dtml-sqlvar username type=string>
|
||||||
|
"""
|
||||||
|
|
4
ZopeProducts/exUserFolder/GroupSource/.cvsignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
32
ZopeProducts/exUserFolder/GroupSource/GroupSource.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# Null Group Source for exUserFolder
|
||||||
|
#
|
||||||
|
# Author: Brent Hendricks <bmh@users.sourceforge.net>
|
||||||
|
# $Id: GroupSource.py,v 1.1 2002/12/02 23:20:49 bmh Exp $
|
||||||
|
from Globals import DTMLFile
|
||||||
|
|
||||||
|
|
||||||
|
manage_addGroupSourceForm=DTMLFile('manage_addGroupSourceForm', globals(), __name__='manage_addGroupSourceForm')
|
||||||
|
|
||||||
|
|
||||||
|
def manage_addGroupSource(dispatcher, REQUEST):
|
||||||
|
""" Add a Group Source """
|
||||||
|
|
||||||
|
# Get the XUF object we're being added to
|
||||||
|
xuf = dispatcher.Destination()
|
||||||
|
|
||||||
|
groupId = REQUEST.get('groupId', None)
|
||||||
|
if groupId:
|
||||||
|
# Invoke the add method for this plugin
|
||||||
|
xuf.groupSources[groupId].manage_addMethod(xuf, REQUEST)
|
||||||
|
else:
|
||||||
|
raise "BadRequest", "Required parameter 'groupId' omitted"
|
||||||
|
|
||||||
|
dispatcher.manage_main(dispatcher, REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSource:
|
||||||
|
pass
|
||||||
|
|
2
ZopeProducts/exUserFolder/GroupSource/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# $Id: __init__.py,v 1.1 2002/12/02 23:20:49 bmh Exp $
|
||||||
|
import GroupSource
|
@ -0,0 +1,33 @@
|
|||||||
|
<dtml-var manage_page_header>
|
||||||
|
<dtml-if currentGroupSource>
|
||||||
|
<dtml-var "MessageDialog(title='Group Source Exists', message='Error: There is already a group source here. Please delete it first', action='manage_main')">
|
||||||
|
<dtml-elif allDone>
|
||||||
|
<dtml-var expr="manage_addGroupSource(REQUEST)">
|
||||||
|
<dtml-elif groupId>
|
||||||
|
<dtml-call "REQUEST.set('groupForm',doGroupSourceForm(groupId=groupId))">
|
||||||
|
<dtml-var "groupForm(mapping=_)">
|
||||||
|
<dtml-else>
|
||||||
|
<dtml-var "DialogHeader(_.None,_,DialogTitle='Add eXtensible User Folder Group Source')">
|
||||||
|
<form action="&dtml-URL;" method="post">
|
||||||
|
<table cellspacing="2">
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<b><dtml-babel src="'en'">Group Source</dtml-babel></b>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select name="groupId">
|
||||||
|
<dtml-in getGroupSources sort="name">
|
||||||
|
<option value="<dtml-var "_['sequence-item'].name">"><dtml-var description></option>
|
||||||
|
</dtml-in>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><br><input type="submit" value=" <dtml-babel src="'en'">Add</dtml-babel> "></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<dtml-var DialogFooter>
|
||||||
|
</dtml-if>
|
||||||
|
<dtml-var manage_page_footer>
|
31
ZopeProducts/exUserFolder/GroupSources/__init__.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:53 akm Exp $
|
||||||
|
|
||||||
|
import nullGroupSource
|
||||||
|
|
||||||
|
# If this fails due to NUG being absent, just skip it
|
||||||
|
try:
|
||||||
|
import zodbGroupSource
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
@ -0,0 +1,2 @@
|
|||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:53 akm Exp $
|
||||||
|
import nullGroupSource
|
@ -0,0 +1,21 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None, _, DialogTitle='Add Basic Group Source', dialog_width='')">
|
||||||
|
<FORM ACTION="&dtml-URL;" METHOD="POST">
|
||||||
|
<dtml-in "REQUEST.form.keys()">
|
||||||
|
<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
|
||||||
|
<dtml-let listVar=sequence-item>
|
||||||
|
<dtml-in "REQUEST[listVar]">
|
||||||
|
<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
|
||||||
|
</dtml-in>
|
||||||
|
</dtml-let>
|
||||||
|
<dtml-else>
|
||||||
|
<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
|
||||||
|
</dtml-if>
|
||||||
|
|
||||||
|
</dtml-in>
|
||||||
|
|
||||||
|
<input type="HIDDEN" name="allDone" value="1">
|
||||||
|
<b><dtml-babel src="'en'">This Group Source has no configuration Items</dtml-babel></b><br>
|
||||||
|
<br>
|
||||||
|
<input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>">
|
||||||
|
</form>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,34 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# Null Group Source for exUserFolder
|
||||||
|
#
|
||||||
|
# Author: Brent Hendricks <bmh@users.sourceforge.net>
|
||||||
|
# $Id: nullGroupSource.py,v 1.1 2004/11/10 14:15:53 akm Exp $
|
||||||
|
from Globals import HTMLFile, INSTANCE_HOME
|
||||||
|
|
||||||
|
from OFS.Folder import Folder
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import PluginRegister
|
||||||
|
from Products.exUserFolder.nullPlugin import nullPlugin
|
||||||
|
|
||||||
|
def manage_addNullGroupSource(self, REQUEST):
|
||||||
|
""" Add a Group Source """
|
||||||
|
self.currentGroupSource=None
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
manage_addNullGroupSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals())
|
||||||
|
manage_editNullGroupSourceForm=None
|
||||||
|
|
||||||
|
|
||||||
|
nullGroupReg=PluginRegister('nullGroupSource',
|
||||||
|
'Null Group Source',
|
||||||
|
nullPlugin,
|
||||||
|
manage_addNullGroupSourceForm,
|
||||||
|
manage_addNullGroupSource,
|
||||||
|
manage_editNullGroupSourceForm)
|
||||||
|
|
||||||
|
exUserFolder.groupSources['nullGroupSource']=nullGroupReg
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
@ -0,0 +1,2 @@
|
|||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:54 akm Exp $
|
||||||
|
import zodbGroupSource
|
@ -0,0 +1,21 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None,_,DialogTitle='Add ZODB Group Source')">
|
||||||
|
|
||||||
|
<FORM ACTION="&dtml-URL;" METHOD="POST">
|
||||||
|
<dtml-in "REQUEST.form.keys()">
|
||||||
|
<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
|
||||||
|
<dtml-let listVar=sequence-item>
|
||||||
|
<dtml-in "REQUEST[listVar]">
|
||||||
|
<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
|
||||||
|
</dtml-in>
|
||||||
|
</dtml-let>
|
||||||
|
<dtml-else>
|
||||||
|
<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
|
||||||
|
</dtml-if>
|
||||||
|
|
||||||
|
</dtml-in>
|
||||||
|
|
||||||
|
<input type="HIDDEN" name="allDone" value="1">
|
||||||
|
<b><dtml-babel src="'en'">This group source requires no user configuration items at this time.</dtml-babel></b><br>
|
||||||
|
<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">NEXT</dtml-babel> ">
|
||||||
|
</FORM>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,7 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None,_,DialogTitle='ZODB Group Source',dialog_width='100%')">
|
||||||
|
<dtml-var manage_tabs>
|
||||||
|
<FORM ACTION="manage_main" METHOD="POST">
|
||||||
|
<b><dtml-babel src="'en'">This group source requires no user configuration items at this time.</dtml-babel></b><br>
|
||||||
|
<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">OK</dtml-babel> ">
|
||||||
|
</FORM>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,177 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# ZODB Group Source for exUserFolder
|
||||||
|
#
|
||||||
|
# Author: Brent Hendricks <mh@users.sourceforge.net>
|
||||||
|
# $Id: zodbGroupSource.py,v 1.1 2004/11/10 14:15:54 akm Exp $
|
||||||
|
from Globals import HTMLFile, MessageDialog, INSTANCE_HOME,Acquisition, PersistentMapping
|
||||||
|
|
||||||
|
from OFS.Folder import Folder
|
||||||
|
|
||||||
|
from Products.ZSQLMethods.SQL import SQL
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import PluginRegister
|
||||||
|
from Products.NuxUserGroups.UserFolderWithGroups import Group, _marker
|
||||||
|
|
||||||
|
import time
|
||||||
|
import zLOG
|
||||||
|
import sys
|
||||||
|
|
||||||
|
manage_addGroupSourceForm=HTMLFile('manage_addzodbGroupSourceForm', globals())
|
||||||
|
|
||||||
|
def manage_addzodbGroupSource(self, REQUEST):
|
||||||
|
""" Add a ZODB Group Source """
|
||||||
|
|
||||||
|
o = zodbGroupSource()
|
||||||
|
self._setObject('zodbGroupSource', o, None, None, 0)
|
||||||
|
o = getattr(self, 'zodbGroupSource')
|
||||||
|
|
||||||
|
# Allow Prop Source to setup default users...
|
||||||
|
if hasattr(o, 'postInitialisation'):
|
||||||
|
o.postInitialisation(REQUEST)
|
||||||
|
self.currentGroupSource=o
|
||||||
|
|
||||||
|
manage_addzodbGroupSourceForm=HTMLFile('manage_addzodbGroupSourceForm', globals())
|
||||||
|
manage_editzodbGroupSourceForm=HTMLFile('manage_editzodbGroupSourceForm', globals())
|
||||||
|
|
||||||
|
#
|
||||||
|
# Very very simple thing, used as an example of how to write a property source
|
||||||
|
# Not recommended for large scale production sites...
|
||||||
|
#
|
||||||
|
|
||||||
|
class zodbGroupSource(Folder):
|
||||||
|
""" Store Group Data inside ZODB, the simplistic way """
|
||||||
|
|
||||||
|
meta_type='Group Source'
|
||||||
|
title='Simplistic ZODB Groups'
|
||||||
|
icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
|
||||||
|
manage_editForm=manage_editzodbGroupSourceForm
|
||||||
|
manage_tabs=Acquisition.Acquired
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.id='zodbGroupSource'
|
||||||
|
self.groups=PersistentMapping()
|
||||||
|
|
||||||
|
|
||||||
|
def addGroup(self, groupname, title='', users=(), **kw):
|
||||||
|
"""Creates a group"""
|
||||||
|
if self.groups.has_key(groupname):
|
||||||
|
raise ValueError, 'Group "%s" already exists' % groupname
|
||||||
|
a = 'before: groupname %s groups %s' % (groupname, self.groups)
|
||||||
|
group = apply(Group, (groupname,), kw)
|
||||||
|
group.setTitle(title)
|
||||||
|
group._setUsers(users)
|
||||||
|
self.groups[groupname] = group
|
||||||
|
|
||||||
|
|
||||||
|
def getGroup(self, groupname, default=_marker):
|
||||||
|
"""Returns the given group"""
|
||||||
|
try:
|
||||||
|
group = self.groups[groupname]
|
||||||
|
except KeyError:
|
||||||
|
if default is _marker: raise
|
||||||
|
return default
|
||||||
|
return group
|
||||||
|
|
||||||
|
|
||||||
|
def delGroup(self, groupname):
|
||||||
|
"""Deletes the given group"""
|
||||||
|
usernames = self.groups[groupname].getUsers()
|
||||||
|
#self.delUsersFromGroup(usernames, groupname)
|
||||||
|
del self.groups[groupname]
|
||||||
|
|
||||||
|
|
||||||
|
def listGroups(self):
|
||||||
|
"""Returns a list of group names"""
|
||||||
|
return tuple(self.groups.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def getGroupsOfUser(self, username):
|
||||||
|
"Get a user's groups"
|
||||||
|
groupnames = []
|
||||||
|
allnames = self.listGroups()
|
||||||
|
groupnames = filter(lambda g, u=username, self=self: u in self.groups[g].getUsers(), allnames)
|
||||||
|
return tuple(groupnames)
|
||||||
|
|
||||||
|
|
||||||
|
def setGroupsOfUser(self, groupnames, username):
|
||||||
|
"Set a user's groups"
|
||||||
|
oldGroups = self.getGroupsOfUser(username)
|
||||||
|
self.delGroupsFromUser(oldGroups, username)
|
||||||
|
self.addGroupsToUser(groupnames, username)
|
||||||
|
|
||||||
|
|
||||||
|
def addGroupsToUser(self, groupnames, username):
|
||||||
|
"Add groups to a user"
|
||||||
|
for name in groupnames:
|
||||||
|
group = self.groups[name]
|
||||||
|
if not username in group.getUsers():
|
||||||
|
group._addUsers([username])
|
||||||
|
|
||||||
|
|
||||||
|
def delGroupsFromUser(self, groupnames, username):
|
||||||
|
"Delete groups from a user"
|
||||||
|
for name in groupnames:
|
||||||
|
group = self.groups[name]
|
||||||
|
if username in group.getUsers():
|
||||||
|
group._delUsers([username])
|
||||||
|
|
||||||
|
|
||||||
|
def getUsersOfGroup(self, groupname):
|
||||||
|
"Get the users in a group"
|
||||||
|
return self.groups[groupname].getUsers()
|
||||||
|
|
||||||
|
|
||||||
|
def setUsersOfGroup(self, usernames, groupname):
|
||||||
|
"Set the users in a group"
|
||||||
|
# uniquify
|
||||||
|
dict = {}
|
||||||
|
for u in usernames: dict[u] = None
|
||||||
|
usernames = dict.keys()
|
||||||
|
|
||||||
|
self.groups[groupname]._setUsers(usernames)
|
||||||
|
|
||||||
|
|
||||||
|
def addUsersToGroup(self, usernames, groupname):
|
||||||
|
"Add users to a group"
|
||||||
|
# uniquify
|
||||||
|
dict = {}
|
||||||
|
for u in usernames: dict[u] = None
|
||||||
|
usernames = dict.keys()
|
||||||
|
|
||||||
|
self.groups[groupname]._addUsers(usernames)
|
||||||
|
|
||||||
|
|
||||||
|
def delUsersFromGroup(self, usernames, groupname):
|
||||||
|
"Delete users from a group"
|
||||||
|
# uniquify
|
||||||
|
dict = {}
|
||||||
|
for u in usernames: dict[u] = None
|
||||||
|
usernames = dict.keys()
|
||||||
|
|
||||||
|
self.groups[groupname]._delUsers(usernames)
|
||||||
|
|
||||||
|
|
||||||
|
def deleteUsers(self, usernames):
|
||||||
|
"Delete a list of users"
|
||||||
|
for user in usernames:
|
||||||
|
groups = self.getGroupsOfUser(user)
|
||||||
|
self.delGroupsFromUser(groups, user)
|
||||||
|
|
||||||
|
|
||||||
|
def postInitialisation(self, REQUEST):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def manage_beforeDelete(self, item, container):
|
||||||
|
# Notify the exUserFolder that it doesn't have a group source anymore
|
||||||
|
container.currentGroupSource=None
|
||||||
|
|
||||||
|
|
||||||
|
zodbGroupReg=PluginRegister('zodbGroupSource','Simplistic ZODB Group Source',
|
||||||
|
zodbGroupSource, manage_addzodbGroupSourceForm,
|
||||||
|
manage_addzodbGroupSource,
|
||||||
|
manage_editzodbGroupSourceForm)
|
||||||
|
exUserFolder.groupSources['zodbGroupSource']=zodbGroupReg
|
BIN
ZopeProducts/exUserFolder/I18N/ZBabelDictionary_txt.zexp
Normal file
91
ZopeProducts/exUserFolder/LICENSE
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
XUF as a whole is covered by the BSD License, however it uses software
|
||||||
|
covered by other compatible licenses (see below)
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
All of the documentation and software included in the exUserFolder
|
||||||
|
Releases is copyrighted by The Internet (Aust) Pty Ltd and contributors
|
||||||
|
ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
|
||||||
|
Copyright 2001, 2002 The Internet (Aust) Pty Ltd
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
This product includes software developed by Digital Creations for use in
|
||||||
|
the Z Object Publishing Environment (http://www.zope.org/)
|
||||||
|
|
||||||
|
Portions of smbAuthSource Copyright (C) 2001 Michael Teo
|
||||||
|
|
||||||
|
Portions of radiusAuthSource Copyright (C) 1999 Stuart Bishop
|
||||||
|
|
||||||
|
fcrypt is Copyright (C) 2001, 2001 Carey Evans
|
||||||
|
|
||||||
|
This product includes cryptographic software written by Eric Young
|
||||||
|
(eay@mincom.oz.au)
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Brief discussion of what the license means to you, not meant to be
|
||||||
|
all encompassing, but, to give you the general idea. This editorial does
|
||||||
|
not need to be distributed d8)
|
||||||
|
|
||||||
|
If you want to incorporate this product (or parts of it) into a commercial
|
||||||
|
product that's fine.
|
||||||
|
|
||||||
|
If you want to modify this product that's fine.
|
||||||
|
|
||||||
|
If you want to modify and distribute this product that's fine (even in
|
||||||
|
commercial products).
|
||||||
|
|
||||||
|
If you want to incorporate this into a larger work that's fine (even
|
||||||
|
if that work has a different license).
|
||||||
|
|
||||||
|
None of the previous items place any obligation of notification, compensation,
|
||||||
|
or return of code to us. In fact we don't care if you do these things. Go
|
||||||
|
forth and prosper. Basically as long as you recognise that this doesn't
|
||||||
|
belong to you, you can do what you want with it even charge money for it.
|
||||||
|
|
||||||
|
Note: If you do distribute this as source, then the XUF components are
|
||||||
|
removable and distributable independently of your license as a whole
|
||||||
|
(although that's a lot of trouble to go to when they could just download it
|
||||||
|
from the same place you did).
|
||||||
|
|
||||||
|
What you can't do, is claim it's yours, and this one thing encompasses a lot
|
||||||
|
of things, here's a few.
|
||||||
|
|
||||||
|
If it's not yours you can't;
|
||||||
|
|
||||||
|
Change the license even if you change the code since the copyright
|
||||||
|
of the modified files remains with the original copyright holders.
|
||||||
|
|
||||||
|
Use bits of it inside products that require the license to change, because
|
||||||
|
only the copyright holders have the right to modify the license (not a
|
||||||
|
concern for commercial projects, only some other Free/Open Source licenses).
|
||||||
|
|
||||||
|
Assign the copyright or other IP to any other party of the whole or any
|
||||||
|
part (even if you change the code), because it's not yours to give away or
|
||||||
|
sell to a 3rd party.
|
||||||
|
|
||||||
|
If the fact you can almost do whatever you want with this code isn't
|
||||||
|
liberal enough for you, contact us and we'll see what we can arrange.
|
27
ZopeProducts/exUserFolder/LoginRequiredMessages.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: LoginRequiredMessages.py,v 1.2 2001/12/01 08:40:03 akm Exp $
|
||||||
|
|
||||||
|
LoginRequiredMessages={
|
||||||
|
'session_expired':'Your Session has Expired',
|
||||||
|
'unauthorized':'Please Login',
|
||||||
|
'login_failed':'Login Failed',
|
||||||
|
}
|
25
ZopeProducts/exUserFolder/MembershipSources/__init__.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000-2005 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id:
|
||||||
|
|
||||||
|
import basicMemberSource
|
||||||
|
import nullMemberSource
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
@ -0,0 +1,22 @@
|
|||||||
|
<dtml-var "DialogHeader(DialogTitle='Change Password', dialog_width='')">
|
||||||
|
<form action="acl_users/manage_changePassword" method="POST">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align="right"><b><dtml-babel src="'en'">Old Password</dtml-babel></b></td>
|
||||||
|
<td><input type="password" name="current_password"></td>
|
||||||
|
<tr>
|
||||||
|
<td align="right"><b><dtml-babel src="'en'">Password</dtml-babel></b></td>
|
||||||
|
<td><input type="password" name="password"></td>
|
||||||
|
</tr>
|
||||||
|
<td align="right"><b><dtml-babel src="'en'">Confirm Password</dtml-babel></b></td>
|
||||||
|
<td><input type="password" name="password_confirm"></td>
|
||||||
|
</tr>
|
||||||
|
<dtml-if "forgottenPasswords=='hint'">
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Password Hint</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="user_hint" value="&dtml.missing-user_hint;"></td>
|
||||||
|
</tr>
|
||||||
|
</dtml-if>
|
||||||
|
</table>
|
||||||
|
<input type="submit" value=" <dtml-babel src="'en'">Change Password</dtml-babel> ">
|
||||||
|
</form>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,31 @@
|
|||||||
|
<dtml-var "DialogHeader(DialogTitle='Signup', dialog_width='')">
|
||||||
|
<form action="acl_users/manage_signupUser" method="POST">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align="right"><b><dtml-babel src="'en'">Username</dtml-babel></td>
|
||||||
|
<td><input name="username" type="text" value="&dtml.missing-username;"></td>
|
||||||
|
</tr>
|
||||||
|
<dtml-if "passwordPolicy=='user'">
|
||||||
|
<tr>
|
||||||
|
<td align="right"><b><dtml-babel src="'en'">Password</dtml-babel></b></td>
|
||||||
|
<td><input type="password" name="password" value="&dtml.missing-password;"></td>
|
||||||
|
</tr>
|
||||||
|
<td align="right"><b><dtml-babel src="'en'">Confirm Password</dtml-babel></b></td>
|
||||||
|
<td><input type="password" name="password_confirm"></td>
|
||||||
|
</tr>
|
||||||
|
<dtml-if "forgottenPasswords=='hint'">
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Password Hint</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="user_hint" value="&dtml.missing-user_hint;"></td>
|
||||||
|
</tr>
|
||||||
|
</dtml-if>
|
||||||
|
</dtml-if>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Real Name</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="user_realname" value="&dtml.missing-user_realname;"></td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'"><dtml-babel src="'en'">Email</dtml-babel></dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="user_email" value="&dtml.missing-user_email;"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<input type="submit" value=" <dtml-babel src="'en'">Signup</dtml-babel> ">
|
||||||
|
</form>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,2 @@
|
|||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $
|
||||||
|
import basicMemberSource
|
@ -0,0 +1,629 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# Basic Membership Source for exUserFolder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: basicMemberSource.py,v 1.1 2004/11/10 14:15:55 akm Exp $
|
||||||
|
|
||||||
|
#
|
||||||
|
# Basically membership is a layer between the signup/login form, and
|
||||||
|
# the authentication layer, it uses the prop source of the users to
|
||||||
|
# store additional information about a user i.e. doesn't impact on the
|
||||||
|
# authentication source.
|
||||||
|
#
|
||||||
|
# Some membership features imply some extra properties for the user will
|
||||||
|
# be available; specifically at this time an email property.
|
||||||
|
#
|
||||||
|
# You also need a MailHost setup and ready to go for emailing stuff to users
|
||||||
|
#
|
||||||
|
|
||||||
|
import string,Acquisition
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
|
||||||
|
from Globals import HTMLFile, INSTANCE_HOME
|
||||||
|
|
||||||
|
from OFS.Folder import Folder
|
||||||
|
from OFS.DTMLMethod import DTMLMethod
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import PluginRegister
|
||||||
|
|
||||||
|
from base64 import encodestring
|
||||||
|
from urllib import quote
|
||||||
|
|
||||||
|
import zLOG
|
||||||
|
|
||||||
|
"""
|
||||||
|
Password Policy enforcement (min/max length, caps etc)
|
||||||
|
Create Password, or User Chooses.
|
||||||
|
Timing out of passwords...
|
||||||
|
Empty Password force change on login...
|
||||||
|
Create Home Directory
|
||||||
|
Copy files from Skelton Directory
|
||||||
|
EMail password hint to user (forgot my password)
|
||||||
|
Reset password and email user (needs plugin?)
|
||||||
|
Redirect on login to fixed or varying per username location.
|
||||||
|
Automatically add users, or manually approve of users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Stupid little things for making a password
|
||||||
|
# Don't hassle me, it's supposed to be basic.
|
||||||
|
|
||||||
|
nouns=['ace', 'ant', 'arc', 'arm', 'axe',
|
||||||
|
'bar', 'bat', 'bee', 'bib', 'bin',
|
||||||
|
'can', 'cap', 'car', 'cat', 'cob',
|
||||||
|
'day', 'den', 'dog', 'dot', 'dux',
|
||||||
|
'ear', 'eel', 'egg', 'elf', 'elk',
|
||||||
|
'fad', 'fan', 'fat', 'fig', 'fez',
|
||||||
|
'gag', 'gas', 'gin', 'git', 'gum',
|
||||||
|
'hag', 'hat', 'hay', 'hex', 'hub']
|
||||||
|
|
||||||
|
pastConjs = [ 'did', 'has', 'was' ]
|
||||||
|
suffixes = [ 'ing', 'es', 'ed', 'ious', 'ily']
|
||||||
|
|
||||||
|
def manage_addBasicMemberSource(self, REQUEST):
|
||||||
|
""" Add a Membership Source """
|
||||||
|
|
||||||
|
pvfeatures=[]
|
||||||
|
minLength=0
|
||||||
|
passwordPolicy=''
|
||||||
|
createHomedir=0
|
||||||
|
homeRoot=''
|
||||||
|
copyFilesFrom=''
|
||||||
|
postLogin=''
|
||||||
|
postSignup=''
|
||||||
|
forgottenPasswords=''
|
||||||
|
defaultRoles=[]
|
||||||
|
usersCanChangePasswords=0
|
||||||
|
baseURL=''
|
||||||
|
loginPage=''
|
||||||
|
signupPage=''
|
||||||
|
passwordPage=''
|
||||||
|
mailHost=''
|
||||||
|
fixedDest=''
|
||||||
|
|
||||||
|
if REQUEST.has_key('basicmember_pvfeatures'):
|
||||||
|
pvfeatures=REQUEST['basicmember_pvfeatures']
|
||||||
|
|
||||||
|
if REQUEST.has_key('basicmember_roles'):
|
||||||
|
defaultRoles=REQUEST['basicmember_roles']
|
||||||
|
|
||||||
|
if not defaultRoles:
|
||||||
|
defaultRoles=['Member']
|
||||||
|
|
||||||
|
if 'minlength' in pvfeatures:
|
||||||
|
minLength=REQUEST['basicmember_minpasslen']
|
||||||
|
|
||||||
|
if REQUEST.has_key('basicmember_passwordpolicy'):
|
||||||
|
passwordPolicy=REQUEST['basicmember_passwordpolicy']
|
||||||
|
|
||||||
|
if REQUEST.has_key('basicmember_createhomedir'):
|
||||||
|
homeRoot=REQUEST['basicmember_homeroot']
|
||||||
|
createHomedir=1
|
||||||
|
|
||||||
|
if REQUEST.has_key('basicmember_copyfiles'):
|
||||||
|
copyFilesFrom=REQUEST['basicmember_copyfiles']
|
||||||
|
|
||||||
|
if REQUEST.has_key('basicmember_changepasswords'):
|
||||||
|
usersCanChangePasswords=1
|
||||||
|
|
||||||
|
if REQUEST.has_key('basicmember_fixeddest'):
|
||||||
|
fixedDest=''
|
||||||
|
|
||||||
|
forgottenPasswords=REQUEST['basicmember_forgottenpasswords']
|
||||||
|
postLogin=REQUEST['basicmember_postlogin']
|
||||||
|
|
||||||
|
baseURL=REQUEST['basicmember_baseurl']
|
||||||
|
loginPage=REQUEST['basicmember_loginpage']
|
||||||
|
signupPage=REQUEST['basicmember_signuppage']
|
||||||
|
passwordPage=REQUEST['basicmember_passwordpage']
|
||||||
|
siteEmail=REQUEST['basicmember_siteemail']
|
||||||
|
siteName=REQUEST['basicmember_sitename']
|
||||||
|
|
||||||
|
mailHost=REQUEST['basicmember_mailhost']
|
||||||
|
|
||||||
|
# postSignup=REQUEST['basicmember_postsignup']
|
||||||
|
|
||||||
|
#
|
||||||
|
# Yep this is obscene
|
||||||
|
#
|
||||||
|
o = BasicMemberSource(pvfeatures, minLength, passwordPolicy,
|
||||||
|
createHomedir, copyFilesFrom, postLogin,
|
||||||
|
homeRoot, forgottenPasswords, defaultRoles,
|
||||||
|
usersCanChangePasswords, baseURL, loginPage,
|
||||||
|
signupPage, passwordPage, mailHost,
|
||||||
|
siteName, siteEmail, fixedDest)
|
||||||
|
|
||||||
|
self._setObject('basicMemberSource', o, None, None, 0)
|
||||||
|
o = getattr(self, 'basicMemberSource')
|
||||||
|
|
||||||
|
if hasattr(o, 'postInitialisation'):
|
||||||
|
o.postInitialisation(REQUEST)
|
||||||
|
|
||||||
|
self.currentMembershipSource=o
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
manage_addBasicMemberSourceForm=HTMLFile('manage_addBasicMemberSourceForm',
|
||||||
|
globals())
|
||||||
|
manage_editBasicMemberSourceForm=HTMLFile('manage_editBasicMemberSourceForm',
|
||||||
|
globals())
|
||||||
|
|
||||||
|
#
|
||||||
|
# Crap, I don't know why I called this basic, I'd hate to see a
|
||||||
|
# complicated one.
|
||||||
|
#
|
||||||
|
class BasicMemberSource(Folder):
|
||||||
|
""" Provide High Level User Management """
|
||||||
|
meta_type="Membership Source"
|
||||||
|
title="Basic Membership Source"
|
||||||
|
icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
|
||||||
|
manage_tabs=Acquisition.Acquired
|
||||||
|
manage_editForm=manage_editBasicMemberSourceForm
|
||||||
|
|
||||||
|
# Ugh...
|
||||||
|
def __init__(self, pvFeatures=[], minLength=0, passwordPolicy='',
|
||||||
|
createHomeDir=0, copyFilesFrom='', postLogin='', homeRoot='',
|
||||||
|
forgottenPasswords='', defaultRoles=[], usersCanChangePasswords=0,
|
||||||
|
baseURL='', loginPage='', signupPage='', passwordPage='',
|
||||||
|
mailHost='', siteName='', siteEmail='', fixedDest=''):
|
||||||
|
|
||||||
|
self.id='basicMemberSource'
|
||||||
|
self.pvFeatures=pvFeatures
|
||||||
|
self.minLength=int(minLength)
|
||||||
|
self.passwordPolicy=passwordPolicy
|
||||||
|
self.createHomeDir=createHomeDir
|
||||||
|
self.copyFilesFrom=copyFilesFrom
|
||||||
|
self.postLogin=postLogin
|
||||||
|
self.homeRoot=homeRoot
|
||||||
|
self.forgottenPasswords=forgottenPasswords
|
||||||
|
self.defaultRoles=defaultRoles
|
||||||
|
self.usersCanChangePasswords=usersCanChangePasswords
|
||||||
|
self.baseURL=baseURL
|
||||||
|
self.loginPage=loginPage
|
||||||
|
self.signupPage=signupPage
|
||||||
|
self.passwordPage=passwordPage
|
||||||
|
self.siteName=siteName
|
||||||
|
self.siteEmail=siteEmail
|
||||||
|
self.fixedDest=fixedDest
|
||||||
|
|
||||||
|
_SignupForm=HTMLFile('SignupForm', globals())
|
||||||
|
SignupForm=DTMLMethod()
|
||||||
|
SignupForm.manage_edit(data=_SignupForm, title='Signup Form')
|
||||||
|
self._setObject('SignupForm', SignupForm)
|
||||||
|
|
||||||
|
_PasswordForm=HTMLFile('PasswordForm', globals())
|
||||||
|
PasswordForm=DTMLMethod()
|
||||||
|
PasswordForm.manage_edit(data=_PasswordForm,
|
||||||
|
title='Change Password')
|
||||||
|
self._setObject('PasswordForm', PasswordForm)
|
||||||
|
|
||||||
|
self.mailHost=mailHost
|
||||||
|
|
||||||
|
_newPasswordEmail=HTMLFile('newPasswordEmail', globals())
|
||||||
|
newPasswordEmail=DTMLMethod()
|
||||||
|
newPasswordEmail.manage_edit(data=_newPasswordEmail,
|
||||||
|
title='Send New Password')
|
||||||
|
self._setObject('newPasswordEmail', newPasswordEmail)
|
||||||
|
|
||||||
|
_forgotPasswordEmail=HTMLFile('forgotPasswordEmail', globals())
|
||||||
|
forgotPasswordEmail=DTMLMethod()
|
||||||
|
forgotPasswordEmail.manage_edit(data=_forgotPasswordEmail,
|
||||||
|
title='Send Forgotten Password')
|
||||||
|
self._setObject('forgotPasswordEmail', forgotPasswordEmail)
|
||||||
|
|
||||||
|
_passwordHintEmail=HTMLFile('passwordHintEmail', globals())
|
||||||
|
passwordHintEmail=DTMLMethod()
|
||||||
|
passwordHintEmail.manage_edit(data=_passwordHintEmail,
|
||||||
|
title='Send Forgotten Password Hint')
|
||||||
|
self._setObject('passwordHintEmail', passwordHintEmail)
|
||||||
|
|
||||||
|
def postInitialisation(self, REQUEST):
|
||||||
|
if self.createHomeDir and self.homeRoot:
|
||||||
|
self.findHomeRootObject()
|
||||||
|
else:
|
||||||
|
self.homeRootObj=None
|
||||||
|
|
||||||
|
if self.copyFilesFrom:
|
||||||
|
self.findSkelRootObject()
|
||||||
|
else:
|
||||||
|
self.homeSkelObj=None
|
||||||
|
|
||||||
|
# The nice sendmail tag doesn't allow expressions for
|
||||||
|
# the mailhost
|
||||||
|
self.mailHostObject=getattr(self, self.mailHost)
|
||||||
|
|
||||||
|
def manage_editMembershipSource(self, REQUEST):
|
||||||
|
""" Edit a basic Membership Source """
|
||||||
|
if REQUEST.has_key('pvfeatures'):
|
||||||
|
self.pvFeatures=REQUEST['pvfeatures']
|
||||||
|
else:
|
||||||
|
self.pvFeatures=[]
|
||||||
|
|
||||||
|
if REQUEST.has_key('minpasslength'):
|
||||||
|
self.minLength=REQUEST['minpasslength']
|
||||||
|
|
||||||
|
if REQUEST.has_key('createhomedir'):
|
||||||
|
createHomeDir=1
|
||||||
|
else:
|
||||||
|
createHomeDir=0
|
||||||
|
|
||||||
|
if createHomeDir:
|
||||||
|
self.copyFilesFrom=REQUEST['copyfiles']
|
||||||
|
if self.copyFilesFrom:
|
||||||
|
self.findSkelRootObject()
|
||||||
|
else:
|
||||||
|
self.homeRoot=REQUEST['homeroot']
|
||||||
|
self.findHomeRootObject()
|
||||||
|
|
||||||
|
if REQUEST.has_key('memberroles'):
|
||||||
|
self.defaultRoles=REQUEST['memberroles']
|
||||||
|
if REQUEST.has_key('changepasswords'):
|
||||||
|
self.usersCanChangePasswords=1
|
||||||
|
else:
|
||||||
|
self.usersCanChangePasswords=0
|
||||||
|
|
||||||
|
self.postLogin=REQUEST['postlogin']
|
||||||
|
if REQUEST.has_key('fixeddest'):
|
||||||
|
self.fixedDest=REQUEST['fixeddest']
|
||||||
|
|
||||||
|
self.baseURL=REQUEST['baseurl']
|
||||||
|
self.loginPage=REQUEST['loginpage']
|
||||||
|
self.signupPage=REQUEST['signuppage']
|
||||||
|
self.passwordPage=REQUEST['passwordpage']
|
||||||
|
self.siteName=REQUEST['sitename']
|
||||||
|
self.siteEmail=REQUEST['siteemail']
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Updated!',
|
||||||
|
message="Membership was Updated",
|
||||||
|
action ='manage_editMembershipSourceForm',
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def forgotPassword(self, REQUEST):
|
||||||
|
username=REQUEST['username']
|
||||||
|
curUser=self.getUser(username)
|
||||||
|
if not curUser:
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='No such user',
|
||||||
|
message="No users matching that username were found.",
|
||||||
|
action ='%s/%s'%(self.baseURL, self.loginPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
userEmail=curUser.getProperty('email')
|
||||||
|
userName=curUser.getProperty('realname')
|
||||||
|
if self.forgottenPasswords == "hint":
|
||||||
|
passwordHint=curUser.getProperty('passwordhint')
|
||||||
|
self.passwordHintEmail(self,
|
||||||
|
REQUEST=REQUEST,
|
||||||
|
username=username,
|
||||||
|
hint=passwordHint,
|
||||||
|
realname=userName,
|
||||||
|
email=userEmail)
|
||||||
|
else:
|
||||||
|
# make a new password, and mail it to the user
|
||||||
|
password = self.generatePassword()
|
||||||
|
curCrypt=self.currentAuthSource.cryptPassword(username,password)
|
||||||
|
|
||||||
|
# Update the user
|
||||||
|
bogusREQUEST={}
|
||||||
|
#bogusREQUEST['username']=username
|
||||||
|
bogusREQUEST['password']=password
|
||||||
|
bogusREQUEST['password_confirm']=password
|
||||||
|
bogusREQUEST['roles']=curUser.roles
|
||||||
|
self.manage_editUser(username, bogusREQUEST)
|
||||||
|
|
||||||
|
self.forgotPasswordEmail(self,
|
||||||
|
REQUEST=REQUEST,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
realname=userName,
|
||||||
|
email=userEmail)
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Sent!',
|
||||||
|
message="Password details have been emailed to you",
|
||||||
|
action ='%s/%s'%(self.baseURL, self.loginPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
def changeProperties(self, REQUEST):
|
||||||
|
|
||||||
|
curUser=self.listOneUser(REQUEST['AUTHENTICATED_USER'].getUserName())
|
||||||
|
curUser=curUser[0]
|
||||||
|
if not curUser:
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Erm!',
|
||||||
|
message="You don't seem to be logged in",
|
||||||
|
action ='%s/%s'%(self.baseURL, self.passwordPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.currentPropSource.updateUser(curUser['username'],REQUEST)
|
||||||
|
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Properties updated',
|
||||||
|
message="Your properties have been updated",
|
||||||
|
action =self.baseURL,
|
||||||
|
REQUEST=REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def changePassword(self, REQUEST):
|
||||||
|
if not self.usersCanChangePasswords:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
curUser=self.listOneUser(REQUEST['AUTHENTICATED_USER'].getUserName())
|
||||||
|
curUser=curUser[0]
|
||||||
|
if not curUser:
|
||||||
|
return self.MessageDialog(
|
||||||
|
title ='Erm!',
|
||||||
|
message="You don't seem to be logged in",
|
||||||
|
action ='%s/%s'%(self.baseURL, self.passwordPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
curCrypt=self.currentAuthSource.cryptPassword(curUser['username'],REQUEST['current_password'])
|
||||||
|
if curCrypt != curUser['password']:
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Password Mismatch',
|
||||||
|
message="Password is incorrect",
|
||||||
|
action ='%s/%s'%(self.baseURL, self.passwordPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
if REQUEST['password'] != REQUEST['password_confirm']:
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Password Mismatch',
|
||||||
|
message="Passwords do not match",
|
||||||
|
action ='%s/%s'%(self.baseURL, self.passwordPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
# OK the old password matches the one the user provided
|
||||||
|
# Both new passwords match...
|
||||||
|
# Time to validate against our normal set of rules...
|
||||||
|
#
|
||||||
|
if not self.validatePassword(REQUEST['password'], curUser['username']):
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Password problem',
|
||||||
|
message="Your password is invalid, please choose another",
|
||||||
|
action ='%s/%s'%(self.baseURL, self.passwordPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
if self.passwordPolicy=='hint':
|
||||||
|
if not hasattr(REQUEST,'user_passwordhint'):
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Password requires hint',
|
||||||
|
message='You must choose a password hint',
|
||||||
|
action ='%s/%s'%(self.baseURL, self.passwordPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
bogusREQUEST={}
|
||||||
|
|
||||||
|
bogusREQUEST['password']=REQUEST['password']
|
||||||
|
bogusREQUEST['password_confirm']=REQUEST['password']
|
||||||
|
bogusREQUEST['roles']=curUser['roles']
|
||||||
|
self.manage_editUser(curUser['username'],bogusREQUEST)
|
||||||
|
# update the cookie so he doesnt have to re-login:
|
||||||
|
if self.cookie_mode:
|
||||||
|
token='%s:%s' %(curUser['username'], REQUEST['password'])
|
||||||
|
token=encodestring(token)
|
||||||
|
token=quote(token)
|
||||||
|
REQUEST.response.setCookie('__ac', token, path='/')
|
||||||
|
REQUEST['__ac']=token
|
||||||
|
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Password updated',
|
||||||
|
message="Your password has been updated",
|
||||||
|
action =self.baseURL,
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
def goHome(self, REQUEST, RESPONSE):
|
||||||
|
redirectstring="%s/%s/%s/manage_main"%(self.baseURL, self.homeRoot, REQUEST.AUTHENTICATED_USER.getUserName())
|
||||||
|
RESPONSE.redirect(redirectstring)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# Tell exUserFolder where we want to go...
|
||||||
|
def getLoginDestination(self, REQUEST):
|
||||||
|
script=''
|
||||||
|
pathinfo=''
|
||||||
|
querystring=''
|
||||||
|
redirectstring=''
|
||||||
|
if self.postLogin=="destination":
|
||||||
|
script=REQUEST['SCRIPT_NAME']
|
||||||
|
pathinfo=REQUEST['PATH_INFO']
|
||||||
|
elif self.postLogin=="varied":
|
||||||
|
script=self.baseURL
|
||||||
|
pathinfo="/acl_users/goHome"
|
||||||
|
|
||||||
|
elif self.postLogin=="fixed":
|
||||||
|
pathinfo="%s"%(self.fixedDest)
|
||||||
|
|
||||||
|
if REQUEST.has_key('QUERY_STRING'):
|
||||||
|
querystring='?'+REQUEST['QUERY_STRING']
|
||||||
|
|
||||||
|
redirectstring=script+pathinfo
|
||||||
|
if querystring:
|
||||||
|
redirectstring=redirectstring+querystring
|
||||||
|
|
||||||
|
return redirectstring
|
||||||
|
|
||||||
|
def validatePassword(self, password, username):
|
||||||
|
if 'minlength' in self.pvFeatures:
|
||||||
|
if len(password) < self.minLength:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if 'mixedcase' in self.pvFeatures:
|
||||||
|
lower = 0
|
||||||
|
upper = 0
|
||||||
|
for c in password:
|
||||||
|
if c in string.lowercase:
|
||||||
|
lower = 1
|
||||||
|
if c in string.uppercase:
|
||||||
|
upper = 1
|
||||||
|
if not upper and lower:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if 'specialchar' in self.pvFeatures:
|
||||||
|
special = 0
|
||||||
|
for c in password:
|
||||||
|
if c in string.punctuation:
|
||||||
|
special = 1
|
||||||
|
break
|
||||||
|
elif c in string.digits:
|
||||||
|
special = 1
|
||||||
|
break
|
||||||
|
if not special:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
#
|
||||||
|
# XXX Move this somewhere else
|
||||||
|
#
|
||||||
|
|
||||||
|
if 'notstupid' in self.pvFeatures:
|
||||||
|
email=''
|
||||||
|
# We try some permutations here...
|
||||||
|
curUser=self.getUser(username)
|
||||||
|
if curUser:
|
||||||
|
email = curUser.getProperty('email')
|
||||||
|
elif hasattr(self, 'REQUEST'):
|
||||||
|
if self.REQUEST.has_key('user_email'): # new signup
|
||||||
|
email=self.REQUEST['user_email']
|
||||||
|
elif self.REQUEST.has_key('email'):
|
||||||
|
email=self.REQUEST['email']
|
||||||
|
|
||||||
|
if ((string.find(password, username)>=0) or
|
||||||
|
( email and
|
||||||
|
(string.find(password,
|
||||||
|
string.split(email,'@')[0]) >=0))):
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# These next two look the same (and they are for now), but, the reason I
|
||||||
|
# Don't use one single method, is I think that SkelObj might migrate to
|
||||||
|
# using full paths, not relative paths.
|
||||||
|
|
||||||
|
def findSkelRootObject(self):
|
||||||
|
# Parent should be acl_users
|
||||||
|
parent = getattr(self, 'aq_parent')
|
||||||
|
|
||||||
|
# This should be the root...
|
||||||
|
root = getattr(parent, 'aq_parent')
|
||||||
|
searchPaths = string.split(self.copyFilesFrom, '/')
|
||||||
|
for o in searchPaths:
|
||||||
|
if not getattr(root, o):
|
||||||
|
break
|
||||||
|
root = getattr(root, o)
|
||||||
|
|
||||||
|
self.homeSkelObj=root
|
||||||
|
|
||||||
|
def findHomeRootObject(self):
|
||||||
|
# Parent should be acl_users
|
||||||
|
parent = getattr(self, 'aq_parent')
|
||||||
|
|
||||||
|
# This should be the root...
|
||||||
|
root = getattr(parent, 'aq_parent')
|
||||||
|
|
||||||
|
searchPaths = string.split(self.homeRoot, '/')
|
||||||
|
for o in searchPaths:
|
||||||
|
if o not in root.objectIds():
|
||||||
|
root.manage_addFolder(id=o, title=o, createPublic=0, createUserF=0)
|
||||||
|
root = getattr(root, o)
|
||||||
|
|
||||||
|
self.homeRootObj=root
|
||||||
|
|
||||||
|
def makeHomeDir(self, username):
|
||||||
|
if not self.homeRootObj:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.homeRootObj.manage_addFolder(id=username, title=username, createPublic=0, createUserF=0)
|
||||||
|
home = getattr(self.homeRootObj, username)
|
||||||
|
|
||||||
|
|
||||||
|
# Allow user to be in charge of their own destiny
|
||||||
|
# XXXX WARNING THIS IS A NORMAL FOLDER *SO USERS CAN ADD ANYTHING*
|
||||||
|
# YOU NEED TO CHANGE THE TYPE OF OBJECT ADDED FOR A USER UNLESS
|
||||||
|
# THIS IS WHAT YOU WANT TO HAPPEN
|
||||||
|
home.manage_addLocalRoles(userid=username, roles=['Manager'])
|
||||||
|
|
||||||
|
if self.copyFilesFrom and self.homeSkelObj and self.homeSkelObj.objectIds():
|
||||||
|
cp=self.homeSkelObj.manage_copyObjects(
|
||||||
|
self.homeSkelObj.objectIds())
|
||||||
|
home.manage_pasteObjects(cp)
|
||||||
|
|
||||||
|
# Fix it so the user owns their stuff
|
||||||
|
curUser=self.getUser(username).__of__(self.aq_parent)
|
||||||
|
home.changeOwnership(curUser, recursive=1)
|
||||||
|
|
||||||
|
def generatePassword(self):
|
||||||
|
password = (choice(nouns) + choice(pastConjs) +
|
||||||
|
choice(nouns) + choice(suffixes))
|
||||||
|
return password
|
||||||
|
|
||||||
|
def createUser(self, REQUEST):
|
||||||
|
if self.passwordPolicy == 'user':
|
||||||
|
if not self.validatePassword(REQUEST['password'], REQUEST['username']):
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Password problem',
|
||||||
|
message='Your password is invalid, please choose another',
|
||||||
|
action ='%s/%s'%(self.baseURL, self.signupPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
if self.passwordPolicy=='hint':
|
||||||
|
if not hasattr(REQUEST,'user_passwordhint'):
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='Password requires hint',
|
||||||
|
message='You must choose a password hint',
|
||||||
|
action ='%s/%s'%(self.baseURL, self.signupPage),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
elif self.passwordPolicy == 'system':
|
||||||
|
REQUEST['password']=self.generatePassword()
|
||||||
|
REQUEST['password_confirm']=REQUEST['password']
|
||||||
|
|
||||||
|
# Email the password.
|
||||||
|
self.newPasswordEmail(self, REQUEST)
|
||||||
|
|
||||||
|
zLOG.LOG("exUserFolder.basicMemberSource", zLOG.BLATHER,
|
||||||
|
"Creating user",
|
||||||
|
"Passed all tests -- creating [%s]" % REQUEST['username'])
|
||||||
|
REQUEST['roles']=self.defaultRoles
|
||||||
|
self.manage_addUser(REQUEST) # Create the User...
|
||||||
|
if self.createHomeDir:
|
||||||
|
self.makeHomeDir(REQUEST['username'])
|
||||||
|
|
||||||
|
return self.MessageDialog(self,
|
||||||
|
title ='You have signed up',
|
||||||
|
message='You have been signed up succesfully',
|
||||||
|
action ='%s'%(self.baseURL),
|
||||||
|
REQUEST=REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
basicMemberReg=PluginRegister('basicMemberSource',
|
||||||
|
'Basic Membership Source',
|
||||||
|
BasicMemberSource,
|
||||||
|
manage_addBasicMemberSourceForm,
|
||||||
|
manage_addBasicMemberSource,
|
||||||
|
manage_editBasicMemberSourceForm)
|
||||||
|
exUserFolder.membershipSources['basicMemberSource']=basicMemberReg
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
<dtml-sendmail mailhost=mailHostObject>
|
||||||
|
To: <dtml-var realname> <<dtml-var email>>
|
||||||
|
From: <dtml-var siteName> <<dtml-var siteEmail>>
|
||||||
|
Subject: You forgot your password for <dtml-var siteName>
|
||||||
|
|
||||||
|
Dear <dtml-var realname>,
|
||||||
|
|
||||||
|
Your username is <dtml-var username> and your password is now
|
||||||
|
<dtml-var password>.
|
||||||
|
|
||||||
|
You should have tested this first, and now that you've tested it, you'll
|
||||||
|
see you need to customise this method.
|
||||||
|
|
||||||
|
</dtml-sendmail>
|
||||||
|
|
@ -0,0 +1,143 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None, _, DialogTitle='Add Basic Membership Source', dialog_width='')">
|
||||||
|
<b><dtml-babel src="'en'">Membership requires a valid property source, you cannot use this with NULL Property Source</dtml-babel></b>
|
||||||
|
<FORM ACTION="&dtml-URL;" METHOD="POST">
|
||||||
|
<dtml-in "REQUEST.form.keys()">
|
||||||
|
<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
|
||||||
|
<dtml-let listVar=sequence-item>
|
||||||
|
<dtml-in "REQUEST[listVar]">
|
||||||
|
<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
|
||||||
|
</dtml-in>
|
||||||
|
</dtml-let>
|
||||||
|
<dtml-else>
|
||||||
|
<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
|
||||||
|
</dtml-if>
|
||||||
|
|
||||||
|
</dtml-in>
|
||||||
|
|
||||||
|
<input type="HIDDEN" name="doGroup" value="1">
|
||||||
|
<table cellspacing="2">
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Site Name (used in emails)</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="basicmember_sitename">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Site Email (used for emails)</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="basicmember_siteemail">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Mail Host</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<select name="basicmember_mailhost">
|
||||||
|
<dtml-in "MailHostIDs()">
|
||||||
|
<option value="<dtml-var sequence-item>">
|
||||||
|
<dtml-var sequence-key></option>
|
||||||
|
</dtml-in>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Site Base</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="basicmember_baseurl"
|
||||||
|
value="<dtml-var "absolute_url()">">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Login Page</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="basicmember_loginpage" value="LoginForm">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Signup Page</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="basicmember_signuppage" value="SignupForm">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Change Password Page</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="basicmember_passwordpage" value="ChangePasswordForm">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Password Validation Features</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<select name="basicmember_pvfeatures:list" multiple>
|
||||||
|
<option value="minlength">Minimum Length</option>
|
||||||
|
<option value="mixedcase">Must have Mixed Case</option>
|
||||||
|
<option value="specichar">Must have Special Chars</option>
|
||||||
|
<option value="notstupid">Not Stupid (username/email/part of name)</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Minimum Length (0 if not required)</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="basicmember_minpasslen:int" value="0">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Password Policy</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<select name="basicmember_passwordpolicy">
|
||||||
|
<option value="user">User Chooses</option>
|
||||||
|
<option value="system">System Chooses and emails User</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Forgotten Passwords</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<select name="basicmember_forgottenpasswords">
|
||||||
|
<option value="hint"><dtml-babel src="'en'">Email a Hint</dtml-babel></option>
|
||||||
|
<option value="reset"><dtml-babel src="'en'">Reset and Email New password</dtml-babel></option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Allow users to change passwords</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="basicmember_changepasswords" checked><dtml-babel src="'en'">Yes</dtml-babel>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Create 'Home Directory'</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="basicmember_createhomedir"><dtml-babel src="'en'">Yes</dtml-babel>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Relative Path to 'Home Directory' Root</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="basicmember_homeroot" value="Members">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Copy initial 'Home Directory' files from...(empty=No Copy)</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="basicmember_copyfiles", value="<dtml-var "_.string.join(getPhysicalPath()[1:], '/')">">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">After login....</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<select name="basicmember_postlogin">
|
||||||
|
<option value="destination"><dtml-babel src="'en'">Go to intended destination</dtml-babel></option>
|
||||||
|
<option value="fixed"><dtml-babel src="'en'">Go to fixed destination</dtml-babel></option>
|
||||||
|
<option value="varied"><dtml-babel src="'en'">Go to Home Directory</dtml-babel></option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Fixed Destination</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="basicmember_fixeddest">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td valign="top" align="right"><b><dtml-babel src="'en'">Default Roles</dtml-babel></b></td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<select name="basicmember_roles:list" size="5" multiple>
|
||||||
|
<dtml-in valid_roles>
|
||||||
|
<dtml-if expr="_vars['sequence-item'] != 'Anonymous'">
|
||||||
|
<dtml-if expr="_vars['sequence-item'] != 'Authenticated'">
|
||||||
|
<dtml-if expr="_vars['sequence-item'] != 'Shared'">
|
||||||
|
<option value="<dtml-var sequence-item html_quote>"><dtml-var sequence-item>
|
||||||
|
</dtml-if>
|
||||||
|
</dtml-if>
|
||||||
|
</dtml-if>
|
||||||
|
</dtml-in valid_roles>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>">
|
||||||
|
</form>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,116 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None, _, DialogTitle='Edit Basic Membership Source', dialog_width='')">
|
||||||
|
<dtml-var manage_tabs>
|
||||||
|
<FORM ACTION="manage_editMembershipSource" METHOD="POST">
|
||||||
|
<dtml-with currentMembershipSource>
|
||||||
|
<table cellspacing="2">
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Site Name (used in emails)</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="sitename" value="&dtml.missing-siteName;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Site Email (used for emails)</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="siteemail" value="&dtml.missing-siteEmail;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Mail Host</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<select name="mailhost">
|
||||||
|
<dtml-in "MailHostIDs()">
|
||||||
|
<option value="<dtml-var sequence-item>"<dtml-if "mailHost==_.getitem('sequence-item',0)"> selected</dtml-if>><dtml-var sequence-key></option>
|
||||||
|
</dtml-in>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Site Base</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="baseurl"
|
||||||
|
value="&dtml.missing-baseURL;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Login Page</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="loginpage" value="&dtml.missing-loginPage;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel>Relative Path (from base) of Signup Page</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="signuppage" value="&dtml.missing-signupPage;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Change Password Page</dtml-babel></b></td>
|
||||||
|
<td><input type="text" name="passwordpage" value="&dtml.missing-passwordPage;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Password Validation Features</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<select name="pvfeatures:list" multiple>
|
||||||
|
<option value="minlength" <dtml-if "'minlength' in pvFeatures"> selected</dtml-if>><dtml-babel src="'en'">Minimum Length</dtml-babel></option>
|
||||||
|
<option value="mixedcase" <dtml-if "'mixedcase' in pvFeatures"> selected</dtml-if>><dtml-babel src="'en'">Must have Mixed Case</dtml-babel></option>
|
||||||
|
<option value="specichar" <dtml-if "'specichar' in pvFeatures"> selected</dtml-if>><dtml-babel src="'en'">Must have Special Chars</dtml-babel></option>
|
||||||
|
<option value="notstupid" <dtml-if "'notstupid' in pvFeatures"> selected</dtml-if>><dtml-babel src="'en'">Not Stupid (username/email/part of name)</dtml-babel></option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Minimum Length (if required)</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="minpasslen:int" value="&dtml.missing-minLength;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Allow users to change passwords</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="changepasswords"<dtml-if usersCanChangePasswords> checked</dtml-if>><dtml-babel src="'en'">Yes</dtml-babel>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Create 'Home Directory'</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="createhomedir"<dtml-if createHomeDir> checked</dtml-if>><dtml-babel src="'en'">Yes</dtml-babel>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Path to 'Home Directory' Root</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="homeroot" value="&dtml.missing-homeRoot;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Copy initial 'Home Directory' files from...(empty=No Copy)</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="copyfiles" value="&dtml.missing-copyFilesFrom;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">After login....</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<select name="postlogin">
|
||||||
|
<option value="destination"<dtml-if "postLogin=='destination'"> selected</dtml-if>><dtml-babel src="'en'">Go to intended destination</dtml-babel></option>
|
||||||
|
<option value="fixed"<dtml-if "postLogin=='fixed'"> selected</dtml-if>><dtml-babel src="'en'">Go to fixed destination</dtml-babel></option>
|
||||||
|
<option value="varied"<dtml-if "postLogin=='varied'"> selected</dtml-if>><dtml-babel src="'en'">Go to Home Directory</dtml-babel></option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td align="right"><b><dtml-babel src="'en'">Fixed Destination</dtml-babel></b></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="fixeddest" value="&dtml.missing-fixedDest;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td valign="top" align="right"><b><dtml-babel src="'en'">Default Roles</dtml-babel></b></td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<select name="memberroles:list" size="5" multiple>
|
||||||
|
<dtml-in valid_roles>
|
||||||
|
<dtml-if expr="_vars['sequence-item'] != 'Anonymous'">
|
||||||
|
<dtml-if expr="_vars['sequence-item'] != 'Authenticated'">
|
||||||
|
<dtml-if expr="_vars['sequence-item'] != 'Shared'">
|
||||||
|
<option value="<dtml-var sequence-item html_quote>"<dtml-if "_['sequence-item'] in defaultRoles"> selected</dtml-if>><dtml-var sequence-item>
|
||||||
|
</dtml-if>
|
||||||
|
</dtml-if>
|
||||||
|
</dtml-if>
|
||||||
|
</dtml-in valid_roles>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<input type="SUBMIT" value=" <dtml-babel src="'en'">Update</dtml-babel> ">
|
||||||
|
</dtml-with>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,16 @@
|
|||||||
|
<dtml-sendmail mailhost=mailHostObject>
|
||||||
|
To: <dtml-var user_realname> <<dtml-var user_email>>
|
||||||
|
From: <dtml-var siteName> <<dtml-var siteEmail>>
|
||||||
|
Subject: Welcome to <dtml-var siteName>
|
||||||
|
|
||||||
|
Dear <dtml-var user_realname>,
|
||||||
|
|
||||||
|
Welcome to <dtml-var siteName>.
|
||||||
|
|
||||||
|
Your username is <dtml-var username> and your password is <dtml-var password>.
|
||||||
|
|
||||||
|
You should have tested this first, and now that you've tested it, you'll
|
||||||
|
see you need to customise this method.
|
||||||
|
|
||||||
|
</dtml-sendmail>
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
<dtml-sendmail mailhost=mailHostObject>
|
||||||
|
To: <dtml-var realname> <<dtml-var email>>
|
||||||
|
From: <dtml-var siteName> <<dtml-var siteEmail>>
|
||||||
|
Subject: Hint for <dtml-var siteName>
|
||||||
|
|
||||||
|
Dear <dtml-var realname>,
|
||||||
|
|
||||||
|
Your username is <dtml-var username> and your password hint was;
|
||||||
|
<dtml-var hint>.
|
||||||
|
|
||||||
|
You should have tested this first, and now that you've tested it, you'll
|
||||||
|
see you need to customise this method.
|
||||||
|
|
||||||
|
</dtml-sendmail>
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
@ -0,0 +1,2 @@
|
|||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $
|
||||||
|
import nullMemberSource
|
@ -0,0 +1,21 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None, _, DialogTitle='Add Basic Membership Source', dialog_width='')">
|
||||||
|
<FORM ACTION="&dtml-URL;" METHOD="POST">
|
||||||
|
<dtml-in "REQUEST.form.keys()">
|
||||||
|
<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
|
||||||
|
<dtml-let listVar=sequence-item>
|
||||||
|
<dtml-in "REQUEST[listVar]">
|
||||||
|
<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
|
||||||
|
</dtml-in>
|
||||||
|
</dtml-let>
|
||||||
|
<dtml-else>
|
||||||
|
<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
|
||||||
|
</dtml-if>
|
||||||
|
|
||||||
|
</dtml-in>
|
||||||
|
|
||||||
|
<input type="HIDDEN" name="doGroup" value="1">
|
||||||
|
<b><dtml-babel src="'en'">This Membership Source has no configuration Items</dtml-babel></b><br>
|
||||||
|
<br>
|
||||||
|
<input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>">
|
||||||
|
</form>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,49 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# Null Membership Source for exUserFolder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: nullMemberSource.py,v 1.1 2004/11/10 14:15:55 akm Exp $
|
||||||
|
from Globals import HTMLFile, INSTANCE_HOME
|
||||||
|
|
||||||
|
from OFS.Folder import Folder
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import PluginRegister
|
||||||
|
from Products.exUserFolder.nullPlugin import nullPlugin
|
||||||
|
|
||||||
|
def manage_addNullMemberSource(self, REQUEST):
|
||||||
|
""" Add a Membership Source """
|
||||||
|
self.currentMembershipSource=None
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
manage_addNullMemberSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals())
|
||||||
|
manage_editNullMemberSourceForm=None
|
||||||
|
|
||||||
|
|
||||||
|
nullMemberReg=PluginRegister('nullMemberSource',
|
||||||
|
'Null Membership Source',
|
||||||
|
nullPlugin,
|
||||||
|
manage_addNullMemberSourceForm,
|
||||||
|
manage_addNullMemberSource,
|
||||||
|
manage_editNullMemberSourceForm)
|
||||||
|
exUserFolder.membershipSources['nullMemberSource']=nullMemberReg
|
||||||
|
|
46
ZopeProducts/exUserFolder/Plugins.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#
|
||||||
|
#
|
||||||
|
# (C) Copyright 2001 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: Plugins.py,v 1.5 2004/11/10 14:15:33 akm Exp $
|
||||||
|
|
||||||
|
import App, Globals, OFS
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
|
||||||
|
from Globals import ImageFile, HTMLFile, HTML, MessageDialog, package_home
|
||||||
|
from OFS.Folder import Folder
|
||||||
|
|
||||||
|
class PluginRegister:
|
||||||
|
def __init__(self, name, description, pluginClass,
|
||||||
|
pluginStartForm, pluginStartMethod,
|
||||||
|
pluginEditForm=None, pluginEditMethod=None):
|
||||||
|
self.name=name #No Spaces please...
|
||||||
|
self.description=description
|
||||||
|
self.plugin=pluginClass
|
||||||
|
self.manage_addForm=pluginStartForm
|
||||||
|
self.manage_addMethod=pluginStartMethod
|
||||||
|
self.manage_editForm=pluginEditForm
|
||||||
|
self.manage_editMethod=pluginEditMethod
|
||||||
|
|
||||||
|
class CryptoPluginRegister:
|
||||||
|
def __init__(self, name, crypto, description, pluginMethod):
|
||||||
|
self.name = name #No Spaces please...
|
||||||
|
self.cryptoMethod = crypto
|
||||||
|
self.description = description
|
||||||
|
self.plugin = pluginMethod
|
28
ZopeProducts/exUserFolder/PropSources/__init__.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $
|
||||||
|
|
||||||
|
|
||||||
|
import nullPropSource
|
||||||
|
# aucune autre prop source pour ScoDoc
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
@ -0,0 +1,2 @@
|
|||||||
|
# $Id: __init__.py,v 1.1 2004/11/10 14:15:56 akm Exp $
|
||||||
|
import nullPropSource
|
@ -0,0 +1,21 @@
|
|||||||
|
<dtml-var "DialogHeader(_.None, _, DialogTitle='Add Basic Property Source', dialog_width='')">
|
||||||
|
<FORM ACTION="&dtml-URL;" METHOD="POST">
|
||||||
|
<dtml-in "REQUEST.form.keys()">
|
||||||
|
<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
|
||||||
|
<dtml-let listVar=sequence-item>
|
||||||
|
<dtml-in "REQUEST[listVar]">
|
||||||
|
<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
|
||||||
|
</dtml-in>
|
||||||
|
</dtml-let>
|
||||||
|
<dtml-else>
|
||||||
|
<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
|
||||||
|
</dtml-if>
|
||||||
|
|
||||||
|
</dtml-in>
|
||||||
|
|
||||||
|
<input type="HIDDEN" name="doMember" value="1">
|
||||||
|
<b><dtml-babel src="'en'">This Property Source has no configuration Items</dtml-babel></b><br>
|
||||||
|
<br>
|
||||||
|
<input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>">
|
||||||
|
</form>
|
||||||
|
<dtml-var DialogFooter>
|
@ -0,0 +1,50 @@
|
|||||||
|
#
|
||||||
|
# Extensible User Folder
|
||||||
|
#
|
||||||
|
# Null Membership Source for exUserFolder
|
||||||
|
#
|
||||||
|
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
|
||||||
|
# ACN: 082 081 472 ABN: 83 082 081 472
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Andrew Milton <akm@theinternet.com.au>
|
||||||
|
# $Id: nullPropSource.py,v 1.1 2004/11/10 14:15:56 akm Exp $
|
||||||
|
from Globals import HTMLFile, INSTANCE_HOME
|
||||||
|
|
||||||
|
from OFS.Folder import Folder
|
||||||
|
|
||||||
|
from Products.exUserFolder.exUserFolder import exUserFolder
|
||||||
|
from Products.exUserFolder.Plugins import PluginRegister
|
||||||
|
from Products.exUserFolder.nullPlugin import nullPlugin
|
||||||
|
|
||||||
|
def manage_addNullPropSource(self, REQUEST):
|
||||||
|
""" Add a Property Source """
|
||||||
|
self.currentPropSource=None
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
manage_addNullPropSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals())
|
||||||
|
manage_editNullPropSourceForm=None
|
||||||
|
|
||||||
|
|
||||||
|
nullPropReg=PluginRegister('nullPropSource',
|
||||||
|
'Null Property Source',
|
||||||
|
nullPlugin,
|
||||||
|
manage_addNullPropSourceForm,
|
||||||
|
manage_addNullPropSource,
|
||||||
|
manage_editNullPropSourceForm)
|
||||||
|
|
||||||
|
exUserFolder.propSources['nullPropSource']=nullPropReg
|
||||||
|
|