removed Zope products
@ -1,5 +0,0 @@
|
||||
|
||||
Produits Zope2 anciens et adaptes pour ScoDoc
|
||||
|
||||
E. Viennet 2013
|
||||
|
@ -1,372 +0,0 @@
|
||||
# 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'
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
# 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')
|
@ -1,209 +0,0 @@
|
||||
# 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
|
@ -1,108 +0,0 @@
|
||||
<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>
|
@ -1,11 +0,0 @@
|
||||
<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>
|
@ -1,84 +0,0 @@
|
||||
<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>
|
@ -1,7 +0,0 @@
|
||||
<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>
|
Before Width: | Height: | Size: 897 B |
Before Width: | Height: | Size: 924 B |
Before Width: | Height: | Size: 930 B |
Before Width: | Height: | Size: 925 B |
Before Width: | Height: | Size: 915 B |
Before Width: | Height: | Size: 929 B |
Before Width: | Height: | Size: 918 B |
Before Width: | Height: | Size: 884 B |
Before Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 918 B |
Before Width: | Height: | Size: 926 B |
Before Width: | Height: | Size: 893 B |
Before Width: | Height: | Size: 894 B |
@ -1,193 +0,0 @@
|
||||
# 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)
|
@ -1,47 +0,0 @@
|
||||
#
|
||||
# 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
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,2 +0,0 @@
|
||||
# $Id: __init__.py,v 1.1 2004/11/10 14:15:36 akm Exp $
|
||||
import pgAuthSource
|
@ -1,40 +0,0 @@
|
||||
<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>
|
@ -1,37 +0,0 @@
|
||||
<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>
|
@ -1,333 +0,0 @@
|
||||
#
|
||||
# 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>
|
||||
"""
|
@ -1,865 +0,0 @@
|
||||
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..
|
@ -1,4 +0,0 @@
|
||||
import pass_crypt
|
||||
import pass_md5
|
||||
import pass_sha
|
||||
import pass_plain
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,35 +0,0 @@
|
||||
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.
|
@ -1,77 +0,0 @@
|
||||
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.]
|
@ -1 +0,0 @@
|
||||
include LICENSE ChangeLog MANIFEST.in
|
@ -1,13 +0,0 @@
|
||||
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
|
@ -1,33 +0,0 @@
|
||||
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
|
@ -1 +0,0 @@
|
||||
import fcrypt
|
@ -1,602 +0,0 @@
|
||||
# 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()
|
@ -1,22 +0,0 @@
|
||||
#!/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'] )
|
@ -1,44 +0,0 @@
|
||||
#
|
||||
# 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
|
@ -1,47 +0,0 @@
|
||||
#
|
||||
# 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
|
@ -1,31 +0,0 @@
|
||||
#
|
||||
# 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
|
@ -1,41 +0,0 @@
|
||||
#
|
||||
# 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
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,20 +0,0 @@
|
||||
# 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
|
@ -1,26 +0,0 @@
|
||||
# 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
|
||||
|
@ -1,140 +0,0 @@
|
||||
# (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>
|
||||
"""
|
||||
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,32 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
@ -1,2 +0,0 @@
|
||||
# $Id: __init__.py,v 1.1 2002/12/02 23:20:49 bmh Exp $
|
||||
import GroupSource
|
@ -1,33 +0,0 @@
|
||||
<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>
|
@ -1,31 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,2 +0,0 @@
|
||||
# $Id: __init__.py,v 1.1 2004/11/10 14:15:53 akm Exp $
|
||||
import nullGroupSource
|
@ -1,21 +0,0 @@
|
||||
<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>
|
@ -1,34 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,2 +0,0 @@
|
||||
# $Id: __init__.py,v 1.1 2004/11/10 14:15:54 akm Exp $
|
||||
import zodbGroupSource
|
@ -1,21 +0,0 @@
|
||||
<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>
|
@ -1,7 +0,0 @@
|
||||
<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>
|
@ -1,177 +0,0 @@
|
||||
#
|
||||
# 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
|
@ -1,91 +0,0 @@
|
||||
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.
|
@ -1,27 +0,0 @@
|
||||
#
|
||||
# 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',
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,22 +0,0 @@
|
||||
<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>
|
@ -1,31 +0,0 @@
|
||||
<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>
|
@ -1,2 +0,0 @@
|
||||
# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $
|
||||
import basicMemberSource
|
@ -1,629 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
@ -1,15 +0,0 @@
|
||||
<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>
|
||||
|
@ -1,143 +0,0 @@
|
||||
<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>
|
@ -1,116 +0,0 @@
|
||||
<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>
|
@ -1,16 +0,0 @@
|
||||
<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>
|
||||
|
@ -1,15 +0,0 @@
|
||||
<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>
|
||||
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,2 +0,0 @@
|
||||
# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $
|
||||
import nullMemberSource
|
@ -1,21 +0,0 @@
|
||||
<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>
|
@ -1,49 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
@ -1,46 +0,0 @@
|
||||
#
|
||||
#
|
||||
# (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
|
@ -1,28 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,2 +0,0 @@
|
||||
# $Id: __init__.py,v 1.1 2004/11/10 14:15:56 akm Exp $
|
||||
import nullPropSource
|
@ -1,21 +0,0 @@
|
||||
<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>
|
@ -1,50 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,122 +0,0 @@
|
||||
#
|
||||
# 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: PropertyEditor.py,v 1.3 2002/01/29 17:42:02 alex_zxc Exp $
|
||||
|
||||
from Globals import DTMLFile, MessageDialog, INSTANCE_HOME
|
||||
from string import join,strip,split,lower,upper,find
|
||||
from urllib import quote, unquote
|
||||
|
||||
|
||||
def editStringProperty( name, value):
|
||||
""" """
|
||||
return('<input type="TEXT" name="%s:string" value="%s">\n'%(name, value))
|
||||
|
||||
def viewStringProperty( name, value):
|
||||
""" """
|
||||
return('<input type="HIDDEN" name="propValue:string" value="%s"><br>%s<br>\n'%(value, value))
|
||||
|
||||
|
||||
def editIntegerProperty( name, value):
|
||||
""" """
|
||||
return('<input type="TEXT" name="%s:int" value="%d">\n'%(name, value or 0))
|
||||
|
||||
def viewIntegerProperty( name, value):
|
||||
""" """
|
||||
return('<input type="HIDDEN" name="propValue:int" value="%d"><br>%d<br>\n'%(value or 0 , value or 0))
|
||||
|
||||
|
||||
def editLongProperty( name, value):
|
||||
""" """
|
||||
return('<input type="TEXT" name="%s:int" value="%d">\n'%(name, value or 0))
|
||||
|
||||
def viewLongProperty( name, value):
|
||||
""" """
|
||||
return('<input type="HIDDEN" name="propValue:long" value="%d"><br>%d<br>\n'%(value or 0, value or 0))
|
||||
|
||||
|
||||
def editFloatProperty( name, value):
|
||||
""" """
|
||||
return('<input type="TEXT" name="%s:float" value="%d">\n'%(name, value))
|
||||
|
||||
def viewFloatProperty( name, value):
|
||||
""" """
|
||||
return('<input type="HIDDEN" name="propValue:float" value="%f"><br>%f<br>\n'%(value, value))
|
||||
|
||||
|
||||
def editListProperty( name, value):
|
||||
a=''
|
||||
if value:
|
||||
a = a + 'Select Items to keep<br>\n'
|
||||
a = a + '<select name="%s:list" multiple>\n'%(name)
|
||||
for i in value:
|
||||
a = a + (
|
||||
'<option value="%s" SELECTED>%s\n'%(i, i))
|
||||
a = a + '</select>\n<br>'
|
||||
a = a + 'Add an item\n<br>'
|
||||
a = a + '<input type="TEXT" name="%s:list">'%(name)
|
||||
return(a)
|
||||
|
||||
def viewListProperty( name, value):
|
||||
a=''
|
||||
if value:
|
||||
for i in value:
|
||||
a = a + (
|
||||
'<input type="HIDDEN" name="propValue:list" value="%s">\n'%(i))
|
||||
a = a + '%s\n<br>'%(i)
|
||||
return(a)
|
||||
|
||||
|
||||
def editDictProperty( name, value):
|
||||
""" """
|
||||
a=''
|
||||
if value and value.keys():
|
||||
for i in value.keys():
|
||||
a = a + '%s : <input type="TEXT" name="%s.%s" value="%s">\n<br>'%(i, name, i, value[i])
|
||||
return a
|
||||
|
||||
|
||||
def viewDictProperty( name, value):
|
||||
""" """
|
||||
a=''
|
||||
if value and value.keys():
|
||||
for i in value.keys():
|
||||
a = a + '%s : <input type="HIDDEN" name="propValue.%s" value="%s">\n<br>'%(i, name, i, value[i])
|
||||
a = a + '%s\n<br>'%(value[i])
|
||||
return a
|
||||
|
||||
EditMethods={'String':editStringProperty,
|
||||
'Integer':editIntegerProperty,
|
||||
'Long':editLongProperty,
|
||||
'Float':editFloatProperty,
|
||||
'List':editListProperty,
|
||||
'Dict':editDictProperty}
|
||||
|
||||
|
||||
ViewMethods={'String':viewStringProperty,
|
||||
'Integer':viewIntegerProperty,
|
||||
'Long':viewLongProperty,
|
||||
'Float':viewFloatProperty,
|
||||
'List':viewListProperty,
|
||||
'Dict':viewDictProperty}
|
||||
|
||||
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
#
|
||||
# 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: __init__.py,v 1.2 2001/12/01 08:40:04 akm Exp $
|
||||
import PropertyEditor
|
@ -1,14 +0,0 @@
|
||||
If you are upgrading an existing site from < 0.50
|
||||
|
||||
I have restructured the 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.
|
||||
|
||||
This means for those of you using SQL or LDAP or any non-ZODB sources,
|
||||
you can remove and then re-add your XUF acl_users to get going again.
|
||||
|
||||
If you are using a ZODB source, then you need to keep the old classes
|
||||
and the old paths around (e.g. symlink zodbAuthSource to
|
||||
AuthSources/zodbAuthSource).
|
@ -1,243 +0,0 @@
|
||||
#
|
||||
# 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: User.py,v 1.10 2004/12/14 05:30:29 akm Exp $
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Zope Public License (ZPL) Version 0.9.4
|
||||
# ---------------------------------------
|
||||
#
|
||||
# Copyright (c) Digital Creations. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that the following
|
||||
# conditions are met:
|
||||
#
|
||||
# 1. Redistributions in source code must retain the above
|
||||
# copyright notice, this list of conditions, and the following
|
||||
# disclaimer.
|
||||
#
|
||||
# 6. Redistributions of any form whatsoever must retain the
|
||||
# following acknowledgment:
|
||||
#
|
||||
# "This product includes software developed by Digital
|
||||
# Creations for use in the Z Object Publishing Environment
|
||||
# (http://www.zope.org/)."
|
||||
#
|
||||
# Disclaimer
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
|
||||
# ANY EXPRESSED 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 DIGITAL CREATIONS OR ITS 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.
|
||||
#
|
||||
##############################################################################
|
||||
from AccessControl.User import BasicUser
|
||||
from string import join,strip,split,lower,upper,find
|
||||
|
||||
class XUFUser(BasicUser):
|
||||
|
||||
icon='misc_/exUserFolder/exUser.gif'
|
||||
|
||||
# cacheable is a dict that must contain at least name, password,
|
||||
# roles, and domains -- unless you're working with your own User class,
|
||||
# in which case you need to override __init__ and define it yourself.
|
||||
def __init__(self, cacheable, propSource, cryptPassword, authSource,
|
||||
groupSource=None):
|
||||
self.name =cacheable['name']
|
||||
self.__ =cacheable['password']
|
||||
if cacheable['roles']:
|
||||
self.roles = filter(None, cacheable['roles'])
|
||||
else:
|
||||
self.roles = []
|
||||
# domains may be passed as a string or a list
|
||||
if type(cacheable['domains']) == type(''):
|
||||
self.domains=filter(None, map(strip,
|
||||
split(cacheable['domains'], ',')))
|
||||
else:
|
||||
self.domains=cacheable['domains']
|
||||
self._authSource=authSource
|
||||
self._propSource=propSource
|
||||
self._groupSource=groupSource
|
||||
self.cryptPassword=cryptPassword
|
||||
|
||||
def getUserName(self):
|
||||
return self.name
|
||||
|
||||
def _getPassword(self):
|
||||
return self.__
|
||||
|
||||
def getRoles(self):
|
||||
return tuple(self.roles) + ('Authenticated',)
|
||||
|
||||
def getDomains(self):
|
||||
return self.domains
|
||||
|
||||
# Ultra generic way of getting, checking and setting properties
|
||||
def getProperty(self, property, default=None):
|
||||
if self._propSource:
|
||||
return self._propSource.getUserProperty(property, self.name, default)
|
||||
|
||||
def hasProperty(self, property):
|
||||
if self._propSource:
|
||||
return self._propSource.hasProperty(property)
|
||||
|
||||
def setProperty(self, property, value):
|
||||
if property[0]=='_':
|
||||
return
|
||||
if self._propSource:
|
||||
return self._propSource.setUserProperty(property, self.name, value)
|
||||
|
||||
def setTempProperty(self, property, value):
|
||||
if property[0]=='_':
|
||||
return
|
||||
if self._propSource:
|
||||
return self._propSource.setTempProperty(property, value)
|
||||
|
||||
def flushTempProperties(self):
|
||||
if self._propSource:
|
||||
return self._propSource.flushTempProperties()
|
||||
|
||||
def delProperty(self, property):
|
||||
if property[0]=='_':
|
||||
return
|
||||
if self._propSource:
|
||||
return self._propSource.delUserProperty(property, self.name)
|
||||
|
||||
def listProperties(self):
|
||||
if self._propSource:
|
||||
return self._propSource.listUserProperties(self.name)
|
||||
|
||||
# Try to allow User['property'] -- won't work for password d;)
|
||||
def __getitem__(self, key):
|
||||
# Don't return 'private' keys
|
||||
if key[0] != '_':
|
||||
if hasattr(self, key):
|
||||
return getattr(self, key)
|
||||
if self._propSource and self._propSource.hasProperty(key):
|
||||
|
||||
return self._propSource.getUserProperty(key, self.name)
|
||||
raise KeyError, key
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key[0]=='_':
|
||||
return
|
||||
if self._propSource:
|
||||
self._propSource.setUserProperty(key, self.name, value)
|
||||
|
||||
# List one user is supplied by the Auth Source...
|
||||
|
||||
def authenticate(self, listOneUser, password, request, remoteAuth=None):
|
||||
result=listOneUser(username=self.name)
|
||||
for people in result:
|
||||
if remoteAuth:
|
||||
return remoteAuth(self.name, password)
|
||||
else:
|
||||
secret=self.cryptPassword(self.name, password)
|
||||
return secret==people['password']
|
||||
return None
|
||||
|
||||
# You can set logout times or whatever here if you want to, the
|
||||
# property source is still active.
|
||||
def notifyCacheRemoval(self):
|
||||
if self._propSource:
|
||||
self._propSource.flushTempProperties()
|
||||
|
||||
# You must override this and __init__ if you are subclassing
|
||||
# the user object, or your user object may not be reconstructed
|
||||
# properly! All values in this dict must be non-Persistent objects
|
||||
# or types, and may not hold any references to Persistent objects,
|
||||
# or the cache will break.
|
||||
def _getCacheableDict(self):
|
||||
return {'name': self.name,
|
||||
'password': self.__,
|
||||
'roles': self.roles,
|
||||
'domains': self.domains}
|
||||
|
||||
def getGroups(self):
|
||||
if self._groupSource:
|
||||
return self._groupSource.getGroupsOfUser(self.name)
|
||||
else:
|
||||
return ()
|
||||
|
||||
|
||||
def _setGroups(self, groupnames):
|
||||
if self._groupSource:
|
||||
return self._groupSource.setGroupsOfUser(groupnames, self.name)
|
||||
|
||||
|
||||
def _addGroups(self, groupnames):
|
||||
if self._groupSource:
|
||||
return self._groupSource.addGroupsToUser(groupnames, self.name)
|
||||
|
||||
def _delGroups(self, groupnames):
|
||||
if self._groupSource:
|
||||
return self._groupSource.delGroupsFromUser(groupnames, self.name)
|
||||
|
||||
def getId(self):
|
||||
if self._propSource and self._propSource.getUserProperty('userid', self.name):
|
||||
return self._propSource.getUserProperty('userid', self.name)
|
||||
return self.name
|
||||
|
||||
|
||||
#
|
||||
# An Anonymous User for session tracking...
|
||||
# Can set and get properties just like a normal user.
|
||||
#
|
||||
# These objects live in the cache, so, we have a __del__ method to
|
||||
# clean ourselves up.
|
||||
#
|
||||
|
||||
class XUFAnonUser(XUFUser):
|
||||
def __init__(self, name, roles, propSource):
|
||||
self.name =name
|
||||
self.__ =''
|
||||
self.roles =filter(None, roles)
|
||||
self._propSource=propSource
|
||||
|
||||
def getRoles(self):
|
||||
return tuple(self.roles) + ('Anonymous',)
|
||||
|
||||
def authenticate(self, listOneUser, password, request, remoteAuth=None):
|
||||
return 1
|
||||
|
||||
def notifyCacheRemoval(self):
|
||||
if self._propSource:
|
||||
self._propSource.deleteUsers([self.name,])
|
||||
|
||||
# We now set up a dummy classes so that people can extend the User objects
|
||||
# or override stuff with much less pain --akm
|
||||
|
||||
class User(XUFUser):
|
||||
pass
|
||||
|
||||
class AnonUser(XUFAnonUser):
|
||||
pass
|
||||
|
@ -1,4 +0,0 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
.*.swp
|
@ -1,409 +0,0 @@
|
||||
#
|
||||
# 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: UserCache.py,v 1.16 2003/07/10 21:11:26 akm Exp $
|
||||
|
||||
# Module Level Caches for Various User Operations
|
||||
|
||||
from time import time
|
||||
from BTrees.OOBTree import OOBTree
|
||||
import threading
|
||||
from Acquisition import aq_inner
|
||||
from Products.exUserFolder.User import User
|
||||
|
||||
class UserCacheItem:
|
||||
lastAccessed=0
|
||||
def __init__(self, username, password, cacheable):
|
||||
self.username=username
|
||||
self.password=password
|
||||
self.cacheable=cacheable
|
||||
self.lastAccessed=time()
|
||||
|
||||
def touch(self):
|
||||
self.lastAccessed=time()
|
||||
|
||||
def __repr__(self):
|
||||
return self.username
|
||||
|
||||
class NegativeUserCacheItem(UserCacheItem):
|
||||
def __init__(self, username):
|
||||
self.username=username
|
||||
self.lastAccessed=time()
|
||||
|
||||
class AdvancedCookieCacheItem(UserCacheItem):
|
||||
lastAccessed=0
|
||||
def __init__(self, username, password):
|
||||
self.username=username
|
||||
self.password=password
|
||||
self.lastAccessed=time()
|
||||
|
||||
class SessionExpiredException(Exception):
|
||||
'User Session Expired'
|
||||
|
||||
|
||||
class UserCache:
|
||||
def __init__(self, sessionLength):
|
||||
self.sessionLength=sessionLength
|
||||
self.cache=OOBTree()
|
||||
self.hits=0
|
||||
self.fail=0
|
||||
self.nouser=0
|
||||
self.attempts=0
|
||||
self.timeouts=0
|
||||
self.cacheStarted=time()
|
||||
self.lock=threading.Lock()
|
||||
|
||||
def addToCache(self, username, password, User):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return
|
||||
|
||||
try:
|
||||
u = self.cache.items(username)
|
||||
if u:
|
||||
for x in self.cache[username]:
|
||||
self.cache.remove(x)
|
||||
except:
|
||||
pass
|
||||
|
||||
u = UserCacheItem(username, password, User._getCacheableDict())
|
||||
self.cache[username]=u
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def getUser(self, caller, username, password, checkpassword=1):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return None
|
||||
|
||||
self.attempts=self.attempts+1
|
||||
|
||||
u = None
|
||||
try:
|
||||
u = self.cache[username]
|
||||
except KeyError:
|
||||
self.nouser=self.nouser+1
|
||||
return None
|
||||
|
||||
now = time()
|
||||
if u:
|
||||
if checkpassword and (u.password != password):
|
||||
self.fail=self.fail+1
|
||||
del self.cache[u.username]
|
||||
elif self.sessionLength and (
|
||||
(now - u.lastAccessed) > self.sessionLength):
|
||||
del self.cache[u.username]
|
||||
self.timeouts=self.timeouts+1
|
||||
user_object=User(u.cacheable,
|
||||
caller.currentPropSource,
|
||||
caller.cryptPassword,
|
||||
caller.currentAuthSource,
|
||||
caller.currentGroupSource)
|
||||
user_object.notifyCacheRemoval()
|
||||
del u
|
||||
raise SessionExpiredException
|
||||
else:
|
||||
u.touch()
|
||||
self.hits=self.hits+1
|
||||
return User(u.cacheable,
|
||||
caller.currentPropSource,
|
||||
caller.cryptPassword,
|
||||
caller.currentAuthSource,
|
||||
caller.currentGroupSource)
|
||||
|
||||
self.nouser=self.nouser+1
|
||||
return None
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def removeUser(self, username):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return
|
||||
try:
|
||||
if self.cache[username]:
|
||||
del self.cache[username]
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def getCacheStats(self):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
return (
|
||||
{'attempts':self.attempts,
|
||||
'hits':self.hits,
|
||||
'fail':self.fail,
|
||||
'misses':self.nouser,
|
||||
'cachesize':len(self.cache),
|
||||
'time':self.cacheStarted,
|
||||
'timeouts':self.timeouts,
|
||||
'length':self.sessionLength})
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def getCurrentUsers(self, caller):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
x=[]
|
||||
now = time()
|
||||
for z in self.cache.keys():
|
||||
u = self.cache[z]
|
||||
if self.sessionLength and (
|
||||
(now - u.lastAccessed) > self.sessionLength):
|
||||
del self.cache[u.username]
|
||||
self.timeouts=self.timeouts+1
|
||||
user_object=User(u.cacheable,
|
||||
caller.currentPropSource,
|
||||
caller.cryptPassword,
|
||||
caller.currentAuthSource,
|
||||
caller.currentGroupSource)
|
||||
user_object.notifyCacheRemoval()
|
||||
del u
|
||||
else:
|
||||
x.append({'username':u.username,
|
||||
'lastAccessed':u.lastAccessed})
|
||||
return x
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
class NegativeUserCache:
|
||||
def __init__(self, sessionLength):
|
||||
self.sessionLength=sessionLength
|
||||
self.cache=OOBTree()
|
||||
self.hits=0
|
||||
self.cacheStarted=time()
|
||||
self.lock=threading.Lock()
|
||||
|
||||
def addToCache(self, username):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return
|
||||
|
||||
try:
|
||||
u = self.cache.items(username)
|
||||
if u:
|
||||
for x in self.cache[username]:
|
||||
self.cache.remove(x)
|
||||
except:
|
||||
pass
|
||||
|
||||
u = NegativeUserCacheItem(username)
|
||||
self.cache[username]=u
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def getUser(self, username):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return 0
|
||||
|
||||
u = None
|
||||
try:
|
||||
u = self.cache[username]
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
now = time()
|
||||
if u:
|
||||
if self.sessionLength and (
|
||||
(now - u.lastAccessed) > self.sessionLength):
|
||||
del self.cache[u.username]
|
||||
else:
|
||||
# We don't touch negative user caches
|
||||
# u.touch()
|
||||
self.hits=self.hits+1
|
||||
return 1
|
||||
return 0
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def removeUser(self, username):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return
|
||||
try:
|
||||
del self.cache[username]
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
class CookieCache:
|
||||
def __init__(self, sessionLength):
|
||||
self.sessionLength=sessionLength
|
||||
self.cache=OOBTree()
|
||||
self.hits=0
|
||||
self.cacheStarted=time()
|
||||
self.lock=threading.Lock()
|
||||
|
||||
def addToCache(self, username, password, key):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return
|
||||
|
||||
try:
|
||||
u = self.cache.items(key)
|
||||
if u:
|
||||
for x in self.cache[key]:
|
||||
self.cache.remove(x)
|
||||
except:
|
||||
pass
|
||||
u = AdvancedCookieCacheItem(username, password)
|
||||
self.cache[key]=u
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def getUser(self, key):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return None
|
||||
|
||||
u = None
|
||||
try:
|
||||
u = self.cache[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
now = time()
|
||||
if u:
|
||||
if self.sessionLength and (
|
||||
(now - u.lastAccessed) > self.sessionLength):
|
||||
del self.cache[key]
|
||||
else:
|
||||
# We don't touch negative user caches
|
||||
# u.touch()
|
||||
self.hits=self.hits+1
|
||||
return u.username, u.password
|
||||
return None
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def removeUser(self, key):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self.sessionLength:
|
||||
return
|
||||
try:
|
||||
del self.cache[key]
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
class GlobalUserCache:
|
||||
caches={}
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def createCache(self, who, sessionLength):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
self.caches[who]=UserCache(sessionLength)
|
||||
return self.caches[who]
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def getCache(self, who):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if self.caches.has_key(who):
|
||||
return self.caches[who]
|
||||
else:
|
||||
return None
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def deleteCache(self, who):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
del self.caches[who]
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
class GlobalNegativeUserCache:
|
||||
caches={}
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def createCache(self, who, sessionLength):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
self.caches[who]=NegativeUserCache(sessionLength)
|
||||
return self.caches[who]
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def getCache(self, who):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if self.caches.has_key(who):
|
||||
return self.caches[who]
|
||||
else:
|
||||
return None
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def deleteCache(self, who):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
del self.caches[who]
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
class GlobalAdvancedCookieCache:
|
||||
caches={}
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def createCache(self, who, sessionLength):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
self.caches[who]=CookieCache(sessionLength)
|
||||
return self.caches[who]
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def getCache(self, who):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if self.caches.has_key(who):
|
||||
return self.caches[who]
|
||||
else:
|
||||
return None
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def deleteCache(self, who):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
del self.caches[who]
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
@ -1,22 +0,0 @@
|
||||
#
|
||||
# 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: __init__.py,v 1.2 2001/12/01 08:40:04 akm Exp $
|
||||
import UserCache
|
@ -1,85 +0,0 @@
|
||||
#
|
||||
# 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: __init__.py,v 1.18 2004/11/10 14:15:33 akm Exp $
|
||||
|
||||
import exUserFolder
|
||||
|
||||
import CryptoSources
|
||||
import AuthSources
|
||||
import PropSources
|
||||
import MembershipSources
|
||||
import GroupSources
|
||||
|
||||
from GroupSource import GroupSource
|
||||
|
||||
from App.ImageFile import ImageFile
|
||||
import OFS
|
||||
|
||||
#
|
||||
# Install a dummy ZBabel setup if we don't have ZBabel installed.
|
||||
#
|
||||
import dummyZBabelTag
|
||||
|
||||
# Methods we need access to from any ObjectManager context
|
||||
legacy_methods = (
|
||||
('manage_addexUserFolderForm', exUserFolder.manage_addexUserFolderForm),
|
||||
('manage_addexUserFolder', exUserFolder.manage_addexUserFolder),
|
||||
('getAuthSources', exUserFolder.getAuthSources),
|
||||
#('getPropSources', exUserFolder.getPropSources),
|
||||
('getCryptoSources', exUserFolder.getCryptoSources),
|
||||
('getMembershipSources', exUserFolder.getMembershipSources),
|
||||
('getGroupSources', exUserFolder.getGroupSources),
|
||||
('doAuthSourceForm', exUserFolder.doAuthSourceForm),
|
||||
#('doPropSourceForm', exUserFolder.doPropSourceForm),
|
||||
('doMembershipSourceForm', exUserFolder.doMembershipSourceForm),
|
||||
# ('doGroupSourceForm', exUserFolder.doGroupSourceForm),
|
||||
('getVariableType', exUserFolder.getVariableType),
|
||||
('DialogHeader', exUserFolder.exUserFolder.DialogHeader),
|
||||
('DialogFooter', exUserFolder.exUserFolder.DialogFooter),
|
||||
#('MailHostIDs', exUserFolder.MailHostIDs),
|
||||
)
|
||||
|
||||
# Image files to place in the misc_ object so they are accesible from misc_/exUserFolder
|
||||
misc_={'exUserFolder.gif': ImageFile('exUserFolder.gif', globals()),
|
||||
'exUserFolderPlugin.gif': ImageFile('exUserFolderPlugin.gif', globals()),
|
||||
'exUser.gif': ImageFile('exUser.gif', globals()),
|
||||
}
|
||||
|
||||
|
||||
def initialize(context):
|
||||
"""
|
||||
Register base classes
|
||||
"""
|
||||
context.registerClass(exUserFolder.exUserFolder,
|
||||
meta_type="ex User Folder",
|
||||
permission="Add exUser Folder",
|
||||
constructors=(exUserFolder.manage_addexUserFolderForm,
|
||||
exUserFolder.manage_addexUserFolder,),
|
||||
legacy=legacy_methods,
|
||||
icon="exUserFolder.gif")
|
||||
|
||||
context.registerClass(GroupSource.GroupSource,
|
||||
meta_type="ex User Folder Group Source",
|
||||
permission="Add exUser Folder",
|
||||
constructors=(GroupSource.manage_addGroupSourceForm,
|
||||
GroupSource.manage_addGroupSource,),
|
||||
icon="exUserFolderPlugin.gif")
|
||||
|
@ -1,4 +0,0 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td></tr></table>
|
@ -1,7 +0,0 @@
|
||||
<table bgcolor="#808080" width="&dtml.missing-dialog_width;"><tr><td align="center" valign="middle">
|
||||
<table width="100%" border="1" bgcolor="White" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center"><table width="100%" bgcolor="#dddddd" border="0" cellspacing="0"><tr><td align="center"><font color="Black" size=+1 face="Helvetica"><b><dtml-babel src="'en'" literal="1"><dtml-var DialogTitle></dtml-babel></b></font></td></tr></table></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
@ -1,41 +0,0 @@
|
||||
<head><body>
|
||||
<FORM ACTION="<dtml-var action>" METHOD="POST" <dtml-if target>TARGET="<dtml-var target>"</dtml-if>>
|
||||
<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>
|
||||
<dtml-var "DialogHeader(DialogTitle=title, dialog_width='')">
|
||||
<TABLE BORDER="0" WIDTH="100%" CELLPADDING="10">
|
||||
<TR>
|
||||
<TD VALIGN="TOP">
|
||||
<BR>
|
||||
<CENTER><B><FONT SIZE="+6" COLOR="#77003B">!</FONT></B></CENTER>
|
||||
</TD>
|
||||
<TD VALIGN="TOP">
|
||||
<BR><BR>
|
||||
<CENTER>
|
||||
<dtml-babel src="'en'" literal="1"><dtml-var message></dtml-babel>
|
||||
</CENTER>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD VALIGN="TOP">
|
||||
</TD>
|
||||
<TD VALIGN="TOP">
|
||||
<CENTER>
|
||||
<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">Ok</dtml-babel> ">
|
||||
</CENTER>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
<dtml-var DialogFooter>
|
||||
</FORM>
|
||||
|
||||
</head></body>
|
@ -1,176 +0,0 @@
|
||||
<dtml-with "_(manage_options=filtered_manage_options())">
|
||||
<dtml-if manage_options>
|
||||
<dtml-call "REQUEST.set('n_', _.len(manage_options)-1)">
|
||||
<dtml-call "REQUEST.set('a_', 0)">
|
||||
<dtml-in manage_options mapping>
|
||||
<dtml-if expr="URL[-(_.len(action)):]==action or
|
||||
URL[-17:]=='/manage_workspace' and _['sequence-start']">
|
||||
<dtml-call "REQUEST.set('a_', _['sequence-index'])">
|
||||
</dtml-if>
|
||||
<dtml-if "_.has_key('management_view') and management_view==label">
|
||||
<dtml-call "REQUEST.set('a_', _['sequence-index'])">
|
||||
</dtml-if>
|
||||
</dtml-in>
|
||||
|
||||
|
||||
<table cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#000000" rowspan="5" width="10%" valign="bottom"
|
||||
align="left"> <img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="2" height="1" alt="" />
|
||||
</td>
|
||||
<td bgcolor="#000000" colspan="<dtml-var "4 * (n_ + 1)">"><img
|
||||
src="&dtml-BASEPATH1;/p_/sp" width="1" height="5" alt="" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<dtml-in manage_options>
|
||||
<dtml-if "_['sequence-index']==a_">
|
||||
<td bgcolor="#ffffff" rowspan="2" valign="top"
|
||||
align="left"><img src="&dtml-BASEPATH1;/p_/ltab" width="5"
|
||||
height="5" alt="" /></td>
|
||||
<td bgcolor="#ffffff"><img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="1" height="2" alt="" /></td>
|
||||
<td bgcolor="#ffffff" rowspan="2" valign="top"
|
||||
align="right"><img src="&dtml-BASEPATH1;/p_/rtab" width="5"
|
||||
height="5" alt="" /></td>
|
||||
<td bgcolor="#000000" rowspan="4"><img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="2" height="1" alt="" /></td>
|
||||
<dtml-else>
|
||||
<td bgcolor="#efefef" rowspan="2" valign="top"
|
||||
align="left"><img src="&dtml-BASEPATH1;/p_/ltab" width="5"
|
||||
height="5" alt="" /></td>
|
||||
<td bgcolor="#efefef"><img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="1" height="2" alt="" /></td>
|
||||
<td bgcolor="#efefef" rowspan="2" valign="top"
|
||||
align="right"><img src="&dtml-BASEPATH1;/p_/rtab" width="5"
|
||||
height="5" alt="" /></td>
|
||||
<td bgcolor="#000000" rowspan="4"><img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="2" height="1" alt="" /></td>
|
||||
</dtml-if>
|
||||
</dtml-in>
|
||||
</tr>
|
||||
<tr>
|
||||
<dtml-in manage_options mapping>
|
||||
<dtml-if "_['sequence-index']==a_">
|
||||
<td bgcolor="#ffffff" valign="bottom" class="tab-small"
|
||||
align="center"><font face="Verdana, Arial, Helvetica"
|
||||
size="1" color="#000000"> <a <dtml-if
|
||||
action>href="&dtml-action;"<dtml-else>href="&dtml-URL1;"</dtml-if
|
||||
><dtml-if target> target="&dtml-target;"</dtml-if
|
||||
>><span style="color: #000000;"><strong><dtml-babel src="'en'" literal="1">
|
||||
<dtml-var label></dtml-babel></strong></span></a> </font></td>
|
||||
<dtml-else>
|
||||
<td bgcolor="#efefef" valign="bottom" class="tab-small"
|
||||
align="center"><font face="Verdana, Arial, Helvetica"
|
||||
size="1" color="#000000"> <a <dtml-if
|
||||
action>href="&dtml-action;"<dtml-else>href="&dtml-URL1;"</dtml-if
|
||||
><dtml-if target> target="&dtml-target;"</dtml-if
|
||||
>><span style="color: #000000;"><strong>
|
||||
<dtml-babel src="'en'" literal="1"><dtml-var label></dtml-babel></strong></span></a> </font></td>
|
||||
</dtml-if>
|
||||
</dtml-in>
|
||||
</tr>
|
||||
<tr>
|
||||
<dtml-in manage_options>
|
||||
<dtml-if "_['sequence-index']==a_">
|
||||
<td colspan="3" bgcolor="#ffffff"><img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="2" height="1" alt="" /></td>
|
||||
<dtml-else>
|
||||
<td colspan="3" bgcolor="#efefef"><img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="2" height="1" alt="" /></td>
|
||||
</dtml-if>
|
||||
</dtml-in>
|
||||
</tr>
|
||||
<tr>
|
||||
<dtml-in manage_options>
|
||||
<dtml-if "_['sequence-index']==a_">
|
||||
<td colspan="3" bgcolor="#ffffff"><img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="2" height="1" alt="" /></td>
|
||||
<dtml-else>
|
||||
<td colspan="3" bgcolor="#c0c0c0"><img src="&dtml-BASEPATH1;/p_/sp"
|
||||
width="2" height="1" alt="" /></td>
|
||||
</dtml-if>
|
||||
</dtml-in>
|
||||
</tr>
|
||||
</table>
|
||||
</dtml-if>
|
||||
|
||||
<dtml-unless MANAGE_TABS_NO_BANNER>
|
||||
<br />
|
||||
<table width="100%" cellspacing="0" cellpadding="2" border="0">
|
||||
<tr class="location-bar">
|
||||
<td align="left" valign="top">
|
||||
<div class="std-text">
|
||||
<dtml-if icon>
|
||||
<img src="&dtml-BASEPATH1;/&dtml-icon;"
|
||||
alt="&dtml-meta_type;" border="0" />
|
||||
</dtml-if>
|
||||
<strong>
|
||||
<dtml-if meta_type>
|
||||
<dtml-if class_manage_path>
|
||||
<a href="&dtml-BASEPATH1;&dtml-class_manage_path;"
|
||||
title="Manage the ZClass of this object">&dtml-meta_type;</a>
|
||||
<dtml-else>
|
||||
&dtml-meta_type;
|
||||
</dtml-if>
|
||||
<dtml-else>
|
||||
Object
|
||||
</dtml-if>
|
||||
at <dtml-var expr="tabs_path_default(REQUEST)">
|
||||
</strong>
|
||||
<dtml-if locked_in_version>
|
||||
<dtml-if modified_in_version>
|
||||
<img src="&dtml-BASEPATH1;/p_/locked"
|
||||
alt="This item has been modified in this version" />
|
||||
<dtml-else>
|
||||
<img src="&dtml-BASEPATH1;/p_/lockedo"
|
||||
alt="This item has been modified in another version" />
|
||||
(<em><dtml-var locked_in_version html_quote></em>)
|
||||
</dtml-if>
|
||||
</dtml-if>
|
||||
<dtml-if wl_isLocked>
|
||||
<img src="&dtml-BASEPATH1;/p_/davlocked"
|
||||
alt="This item has been locked by WebDAV"
|
||||
title="This item has been locked by WebDAV" />
|
||||
</dtml-if wl_isLocked>
|
||||
</div>
|
||||
</td>
|
||||
<dtml-if "_.has_key('help_topic') and _.has_key('help_product')">
|
||||
<td align="right" valign="top">
|
||||
<div class="std-text">
|
||||
<dtml-var "HelpSys.helpLink(help_product, help_topic)">
|
||||
</div>
|
||||
</td>
|
||||
<dtml-else>
|
||||
<dtml-if manage_options>
|
||||
<dtml-with "_(option=manage_options[a_])">
|
||||
<dtml-if "option.has_key('help')">
|
||||
<td align="right" valign="top">
|
||||
<div class="std-text">
|
||||
<dtml-var "HelpSys.helpLink(option['help'][0], option['help'][1])">
|
||||
</div>
|
||||
</td>
|
||||
</dtml-if>
|
||||
</dtml-with>
|
||||
</dtml-if>
|
||||
</dtml-if>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<dtml-if Zope-Version>
|
||||
<div class="system-msg">
|
||||
<em>You are currently working in version <a href="&dtml-SERVER_URL;&dtml-Zope-Version;/manage_main"><dtml-var Zope-Version html_quote></a></em>
|
||||
</div>
|
||||
</dtml-if>
|
||||
</dtml-unless>
|
||||
|
||||
<dtml-if manage_tabs_message>
|
||||
<div class="system-msg">
|
||||
<dtml-var manage_tabs_message>
|
||||
(<dtml-var ZopeTime fmt="%Y-%m-%d %H:%M">)
|
||||
</div>
|
||||
</dtml-if>
|
||||
|
||||
</dtml-with>
|
@ -1,19 +0,0 @@
|
||||
<dtml-with "_(manage_options=filtered_manage_options())">
|
||||
<dtml-if manage_options>
|
||||
<table cellpadding="0" cellspacing="0" width="100%" border="2">
|
||||
<tr><td>
|
||||
<table cellpadding="0" cellspacing="5" width="100%" border="0">
|
||||
<tr>
|
||||
<td valign="bottom" align="left" class="tab-small">
|
||||
<dtml-in manage_options mapping>
|
||||
<b><a <dtml-if action>href="<dtml-var action>"
|
||||
<dtml-else>href="<dtml-var URL1>" </dtml-if>
|
||||
<dtml-if target> target="<dtml-var target>"</dtml-if>>
|
||||
[<dtml-babel src="'en'" literal="1"><dtml-var label></dtml-babel>]</a></b>
|
||||
</dtml-in>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td></tr></table>
|
||||
</dtml-if>
|
||||
</dtml-with>
|
@ -1,119 +0,0 @@
|
||||
Frequently Asked Questions
|
||||
|
||||
1. Why shouldn't I use Core Session Tracking + Login Manager?
|
||||
XUF serves a different set of users to the combination above. XUF
|
||||
aims to be a simple out of the box solution. Login Manager allows
|
||||
for very complex authorisation schemes that can query multiple user
|
||||
sources. We don't do that.
|
||||
|
||||
2. Why use XUF at all?
|
||||
In its simplest configuration, XUF provides the same functionality
|
||||
as the standard User Folder, but, is more secure. Passwords are
|
||||
stored encrypted, which is not the case for the standard User Folder.
|
||||
So even if you don't want to set properties on users, or any
|
||||
membership facilities, there is a benefit to running XUF.
|
||||
|
||||
3. Do I have to have all this other stuff?
|
||||
No. The only thing you need to enable is authentication. There is
|
||||
a null property source, and a null membership source. Everything
|
||||
other than authentication is optional.
|
||||
|
||||
4. Can I use it as a root folder?
|
||||
Some people have reported success in doing so. We don't recommend
|
||||
it for various reasons. The main one is that the internal Zope API
|
||||
can change without warning, which could break XUF and lock you out
|
||||
of your Zope. This can happen with any User Folder product. We
|
||||
recommend you look at VHM and other Site Access methods to allow
|
||||
you to store your main site in a sub-folder.
|
||||
|
||||
5. When will XUF support authentication against XYZ system?
|
||||
That depends. First the active developers need to have an interest
|
||||
in it, and more importantly they need to be able to test it. Writing
|
||||
your authentication method is very simple, so if you understand
|
||||
what you want to authenticate against, and know some python you
|
||||
could write one in an hour. You can also use the usAuthSource to
|
||||
write one using PythonScripts, ExternalMethods, DTML, or any other
|
||||
callable method that Zope supports.
|
||||
|
||||
6. I wrote this cool authentication source can I get it into the main
|
||||
distribution?
|
||||
Yes and No. If your authentication is Open Source, and has a
|
||||
compatible license with XUF, and doesn't require any external
|
||||
libraries, odds are it'll go into the main distribution. If it
|
||||
depends on external libraries, it's possible it can conditionally
|
||||
go into the main distribution. The nice thing about XUF is that
|
||||
Authentication, Property, and Membership sources are all packagable
|
||||
as independent products, so you can distribute it as a standalone
|
||||
product, and it'll work (without having to have the code drop into
|
||||
the XUF directory either).
|
||||
|
||||
7. Is XUF going to be part of the Core Zope?
|
||||
No idea. At the moment (0.10.5) XUF is probably not at a level that
|
||||
Zope Corporation would consider mature enough for core inclusion
|
||||
anyway.
|
||||
|
||||
Actually the answer now, is probably not. At a minimum smbAuthSource,
|
||||
and radiusAuthSource would have to be stripped and distributed
|
||||
seperately. Over and above that, I would have to assign Zope Corp
|
||||
co-ownership rights on the IP, which amongst other things gives
|
||||
them or anyone that buys them unlimited access to future derived
|
||||
works. I refuse to do this on principle, the liberal licensing of
|
||||
the product should be more than adequate for any (especially open
|
||||
source) endeavour.
|
||||
|
||||
8. What's with the Management Screens?
|
||||
It's a joke on the Zope World.
|
||||
|
||||
9. But they're really ugly I want to change them.
|
||||
That's fine, you do that, that's the point.
|
||||
|
||||
10. Can I send you patches to put them back to standard Management
|
||||
Screens?
|
||||
You can put patches into the tracker at Source Forge if you want to.
|
||||
|
||||
11. HELP!!!! I tried to install XUF as my root folder, without
|
||||
reading the FAQ, or really knowing what I'm doing, and now I'm
|
||||
hosed!!!
|
||||
That's a shame.
|
||||
|
||||
12. Will XUF work with ZEO?
|
||||
Unknown. However, it's almost certain that in its current form
|
||||
credential caching will not work across ZEO -- you will get a
|
||||
seperate User Cache for each Zope Client (which isn't really all
|
||||
that bad). However, it means that if you want to use Session Tracking,
|
||||
you need to lock users to one particular server. Most commercial
|
||||
Load Balancers do this for you anyhow. A persistent cache will form
|
||||
part of an upcoming release which will allow all of this to work
|
||||
transparently across ZEO farms.
|
||||
|
||||
13. Shouldn't it be EUF?
|
||||
No, it should be XUF :-P
|
||||
|
||||
14. How can I log in a user via a form submission?
|
||||
Yes, the key is sending the __ac_name and __ac_password (yes that's
|
||||
two underscores in front) as form variables to any object covered
|
||||
by the XUF.
|
||||
|
||||
This form will take your users to the /index_html and log them in.
|
||||
You can place this anywhere in your site including /index_html.
|
||||
|
||||
<form action="/index_html">
|
||||
Name: <input type="text" size="20" name="__ac_name"><br>
|
||||
Password: <input type="password" size="20" name="__ac_password">
|
||||
<input type="submit" value="Log in">
|
||||
</form>
|
||||
|
||||
15. That Dialog box sure is ugly! How can I customize it so it looks
|
||||
right in my site?
|
||||
Under the contents tab add an object called MessageDialog , it can
|
||||
be a dtml document, method, or even a page template. Make it look
|
||||
how you want, it will acquire all objects from the folder the XUF
|
||||
is in so you can use the standard_html_header, call scripts, etc.
|
||||
|
||||
16. Why can't I change the default crypto method?
|
||||
Because someone will change it, and all the existing passwords will cease
|
||||
to work. Then I'll get lots of hate-mail, or get slagged off on other mailing
|
||||
lists :-)
|
||||
|
||||
17. Where is the Zen Master's Guide to exUserFolder?
|
||||
Everywhere and nowhere.
|