Author: tmckay
Date: 2012-05-31 14:49:15 +0000 (Thu, 31 May 2012)
New Revision: 5395
Added:
branches/stability/cumin/model/access/
branches/stability/cumin/python/cumin/authenticator.py
branches/stability/cumin/python/cumin/persona.py
Modified:
branches/stability/cumin/Makefile
branches/stability/cumin/bin/cumin-admin
branches/stability/cumin/bin/cumin-database
branches/stability/cumin/bin/cumin-web
branches/stability/cumin/etc/cumin.conf
branches/stability/cumin/python/cumin/account/widgets.py
branches/stability/cumin/python/cumin/account/widgets.strings
branches/stability/cumin/python/cumin/admin.py
branches/stability/cumin/python/cumin/config.py
branches/stability/cumin/python/cumin/database.py
branches/stability/cumin/python/cumin/grid/dashboard.py
branches/stability/cumin/python/cumin/grid/job.py
branches/stability/cumin/python/cumin/grid/limit.py
branches/stability/cumin/python/cumin/grid/main.py
branches/stability/cumin/python/cumin/grid/negotiator.py
branches/stability/cumin/python/cumin/grid/pool.py
branches/stability/cumin/python/cumin/grid/quota.py
branches/stability/cumin/python/cumin/grid/scheduler.py
branches/stability/cumin/python/cumin/grid/submission.py
branches/stability/cumin/python/cumin/grid/tags.py
branches/stability/cumin/python/cumin/inventory/main.py
branches/stability/cumin/python/cumin/inventory/system.py
branches/stability/cumin/python/cumin/main.py
branches/stability/cumin/python/cumin/messaging/binding.py
branches/stability/cumin/python/cumin/messaging/brokerlink.py
branches/stability/cumin/python/cumin/messaging/connection.py
branches/stability/cumin/python/cumin/messaging/exchange.py
branches/stability/cumin/python/cumin/messaging/main.py
branches/stability/cumin/python/cumin/messaging/queue.py
branches/stability/cumin/python/cumin/model.py
branches/stability/cumin/python/cumin/objectframe.py
branches/stability/cumin/python/cumin/objectselector.py
branches/stability/cumin/python/cumin/server.py
branches/stability/cumin/python/cumin/stat.py
branches/stability/cumin/python/cumin/usergrid/main.py
branches/stability/cumin/python/cumin/util.py
branches/stability/cumin/python/cumin/widgets.py
branches/stability/mint/python/mint/database.py
branches/stability/mint/python/mint/main.py
branches/stability/rosemary/python/rosemary/model.py
branches/stability/wooly/python/wooly/__init__.py
branches/stability/wooly/python/wooly/forms.py
branches/stability/wooly/python/wooly/pages.py
branches/stability/wooly/python/wooly/server.py
branches/stability/wooly/python/wooly/table.py
branches/stability/wooly/python/wooly/util.py
branches/stability/wooly/python/wooly/widgets.py
branches/stability/wooly/python/wooly/wsgiserver/ssl_builtin.py
branches/stability/wooly/python/wooly/wsgiserver/ssl_pyopenssl.py
Log:
Add support for LDAP, web server SSL, and role enforcement
BZ737979
BZ820861
BZ769573
Modified: branches/stability/cumin/Makefile
===================================================================
--- branches/stability/cumin/Makefile 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/Makefile 2012-05-31 14:49:15 UTC (rev 5395)
@@ -27,8 +27,10 @@
install -pm 0644 ../wooly/LICENSE-for-wsgiserver ${CUMIN_HOME}/doc
install -pm 0644 ../wooly/COPYING-for-wsgiserver ${CUMIN_HOME}/doc
install -d ${CUMIN_HOME}/model/upgrades
+ install -d ${CUMIN_HOME}/model/access
install -pm 0644 model/*.xml ${CUMIN_HOME}/model
- install -pm 0755 model/upgrades/* ${CUMIN_HOME}/model/upgrades/
+ install -pm 0644 model/access/* ${CUMIN_HOME}/model/access/
install -d ${CUMIN_HOME}/resources
install -pm 0644 ../wooly/resources/* ${CUMIN_HOME}/resources
install -pm 0644 resources/* ${CUMIN_HOME}/resources
Modified: branches/stability/cumin/bin/cumin-admin
===================================================================
--- branches/stability/cumin/bin/cumin-admin 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/bin/cumin-admin 2012-05-31 14:49:15 UTC (rev 5395)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
import os
import sys
@@ -6,7 +6,7 @@
import subprocess
from subprocess import PIPE
-from psycopg2 import IntegrityError
+from psycopg2 import IntegrityError, OperationalError
home = os.environ.get("CUMIN_HOME",
os.path.normpath("/usr/share/cumin"))
sys.path.append(os.path.join(home, "python"))
@@ -16,6 +16,7 @@
from cumin import *
from cumin.config import *
from cumin.util import *
+from cumin.admin import SchemaMissing
def main():
uid = os.getuid()
@@ -65,18 +66,46 @@
sys.exit(1)
broker_uris = [x.strip() for x in opts.brokers.split(",")]
+ authmech = [x.strip() for x in values.common.auth.split(";") if x !=
""]
- app = Cumin(config.get_home(), broker_uris, opts.database)
+ app = Cumin(config.get_home(), broker_uris, opts.database, authmech=authmech)
app.check()
- app.init()
+ # Normally, a schema version check during Cumin init will raise
+ # an exception if the schema version doesn't match. However,
+ # cumin-admin is the mechanism for fixing the schema so we
+ # have to avoid the check here!
+ try:
+ app.db_init(schema_version_check=False)
+ except OperationalError:
+ print "Can't talk to the database"
+ error("Run 'cumin-database check' as root for more
information")
+
conn = app.database.get_connection()
cursor = conn.cursor()
+
+ # Okay, do the schema version check here and disallow
+ # all but schema commands if the version is wrong
+ if name not in ["check_schema",
+ "create_schema",
+ "drop_schema",
+ "print_schema",
+ "upgrade_schema"]:
+ err = True
+ try:
+ app.admin.check_schema(cursor)
+ err = False
+ except SchemaMissing:
+ print("The schema is missing, run 'cumin-admin
create-schema'")
+ except SchemaVersion, e:
+ print str(e)
+ print("Run 'cumin-admin upgrade-schema'")
+ if err:
+ error("Only schema commands are allowed until the schema is
repaired")
try:
handler(app, cursor, opts, args[1:])
-
conn.commit()
finally:
cursor.close()
@@ -96,6 +125,11 @@
lines.append("User commands:")
lines.append("")
lines.append(" add-user USER [PASSWORD] Add USER")
+ lines.append(" external-user USER Add USER with external
authentication.")
+ lines.append(" Use this command when a role other
than 'user'")
+ lines.append(" will be set for this external
user. Otherwise,")
+ lines.append(" this command is not
necessary.")
+ lines.append("")
lines.append(" remove-user USER Remove USER")
lines.append(" add-assignment USER ROLE Add USER to ROLE")
lines.append(" remove-assignment USER ROLE Remove USER from ROLE")
@@ -104,6 +138,8 @@
lines.append(" list-roles List roles")
lines.append(" export-users FILE Export user list to file")
lines.append(" import-users FILE Import user list from file")
+ lines.append(" expunge-users Remove all users. Use with
caution")
+ lines.append(" You may want to run export-users
first")
lines.append("")
lines.append("Schema commands:")
lines.append("")
@@ -184,7 +220,11 @@
print app.admin.get_schema(),
def handle_upgrade_schema(app, cursor, opts, args):
- curr = app.admin.get_schema_version(cursor)
+ try:
+ curr = app.admin.get_schema_version(cursor)
+ except SchemaMissing:
+ error("The schema is missing, run 'cumin-admin
create-schema'")
+
target = app.admin.get_target_schema_version()
if curr == target:
print "The schema is already version %s, nothing to do" % target
@@ -243,16 +283,20 @@
def handle_check_schema(app, cursor, opts, args):
try:
schema_version = app.admin.check_schema(cursor)
+ except SchemaMissing:
+ error("The schema is missing, run 'cumin-admin
create-schema'")
except Exception, e:
error(str(e))
print "The schema is OK (schema version %s)" % schema_version
def handle_drop_schema(app, cursor, opts, args):
- app.admin.drop_schema(cursor)
+ try:
+ app.admin.drop_schema(cursor)
+ print "The schema is dropped"
+ except SchemaMissing:
+ print "The schema has already been dropped"
- print "The schema is dropped"
-
def get_users(app, cursor):
user_cls = app.model.com_redhat_cumin.User
role_cls = app.model.com_redhat_cumin.Role
@@ -281,24 +325,102 @@
def handle_list_users(app, cursor, opts, args):
- print " ID Name Roles"
- print "---- -------------------- --------------------"
+ print " ID Name Roles"
+ print "---- -------------------- --------------------"
users, roles_by_user_id = get_users(app, cursor)
-
+ externals = 0
for user in users:
try:
roles = ", ".join(roles_by_user_id[user._id])
except KeyError:
roles = ""
+ if len(user.password) == 0:
+ ex = "*"
+ externals += 1
+ else:
+ ex = " "
+ print "%4i %s %-20s %-20s" % (user._id, ex, user.name, roles)
- print "%4i %-20s %-20s" % (user._id, user.name, roles)
-
count = len(users)
print
print "(%i user%s found)" % (count, ess(count))
+ if externals > 0:
+ print "(%i external user%s, indicated by *)" % (externals,
ess(externals))
+def handle_external_user(app, cursor, opts, args):
+ try:
+ name = args[0]
+ except IndexError:
+ error("USER is required")
+ role = app.admin.get_role(cursor, "user")
+ try:
+ user = app.admin.add_user(cursor, name, "")
+ except IntegrityError:
+ error("A user called '%s' already exists" % name)
+
+ app.admin.add_assignment(cursor, user, role)
+ print "External user '%s' is added" % name
+
+def handle_external_sync(app, cursor, opts, args):
+ print "This command is not supported at this time."
+ return
+
+ try:
+ authenticatorname = args[0]
+ except IndexError:
+ error("AUTHENTICATOR name is required")
+
+ output_on = False
+ if len(args) > 1:
+ output_on = args[1].lower() in ("t", "true", "1")
+
+ users, conflicts, errors = \
+ app.authenticator.batch_import(authenticatorname, output_on)
+
+ print("Successfully imported %s users, %s conflicts" % (users, conflicts))
+ if errors:
+ print("Errors reported, check logs for details.")
+
+def handle_expunge_users(app, cursor, opts, args):
+
+ print("This will remove all users from Cumin's database.")
+ print("Hint: you may want to backup Cumin's user database "\
+ "with export-users first.\n")
+ try:
+ go = confirm( "Are you sure you want to do this?", resp=False)
+ except KeyboardInterrupt:
+ go = False
+
+ if go:
+ cls = app.model.com_redhat_cumin.User
+ cls.delete_selection(cursor)
+ print("\nAll users removed.")
+ else:
+ print("\nNo users removed.")
+
+
+def handle_add_role(app, cursor, opts, args):
+ print("\nNot implemented.")
+ return
+
+ # We may allow this later
+ try:
+ name = args[0]
+ except IndexError:
+ error("ROLE is required")
+
+ if app.admin.get_role(cursor, name):
+ error("A role called '%s' already exists" % name)
+
+ try:
+ app.admin.add_role(cursor, name)
+ except IntegrityError:
+ error("A role called '%s' already exists" % name)
+
+ print "Role '%s' is added" % name
+
def handle_add_user(app, cursor, opts, args):
try:
name = args[0]
@@ -314,18 +436,14 @@
password = args[1]
except IndexError:
password = prompt_password()
-
crypted = crypt_password(password)
-
role = app.admin.get_role(cursor, "user")
-
try:
user = app.admin.add_user(cursor, name, crypted)
except IntegrityError:
error("A user called '%s' already exists" % name)
app.admin.add_assignment(cursor, user, role)
-
print "User '%s' is added" % name
def handle_export_users(app, cursor, opts, args):
@@ -365,15 +483,19 @@
for info in user_data:
line += 1
+ count = 0
for field in info:
# Don't suppose it's possible for data to be anything other than
# strings from csv, but this is where such type checking goes
if type(field) != str:
error("Data error, line %u, fields must be strings" % line)
- if len(field) == 0:
- error("Data error, line %u, importer "\
- "does not allow empty fields" % line)
+ # Empty password field is external auth indicator, allow it
+ if len(field) == 0 and count != 1:
+ error("Data error, line %u, field %u, importer "\
+ "does not allow empty field" % (line, field+1))
+ count += 1
+
if len(info) < 2:
error("Data error, line %u, not enough fields. "\
" User and password are required" % (line))
@@ -388,7 +510,7 @@
# before we get an integrity error
warn("A user called '%s' already exists, skipping" %
info[0])
continue
-
+
try:
user = app.admin.add_user(cursor, info[0], info[1])
except IntegrityError:
@@ -439,7 +561,6 @@
def handle_remove_assignment(app, cursor, opts, args):
user, role = get_user_and_role(app, cursor, args)
-
assignment = app.admin.get_assignment(cursor, user, role)
if not assignment:
@@ -464,6 +585,11 @@
user = app.admin.get_user(cursor, user_name)
role = app.admin.get_role(cursor, role_name)
+ if not user:
+ error("Specified user %s does not exist" % user_name)
+ if not role:
+ error("Specified role %s does not exist" % role_name)
+
return user, role
def handle_change_password(app, cursor, opts, args):
@@ -476,10 +602,11 @@
if not user:
error("User '%s' is not found" % user_name)
-
+ if user.password == "":
+ error("User '%s' is an external user, password cannot be changed
"\
+ "through this mechanism" % user_name)
user.password = crypt_password(prompt_password())
user.save(cursor)
-
print "Password of user '%s' is changed" % user.name
def handle_load_demo_data(app, cursor, opts, args):
Modified: branches/stability/cumin/bin/cumin-database
===================================================================
--- branches/stability/cumin/bin/cumin-database 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/bin/cumin-database 2012-05-31 14:49:15 UTC (rev 5395)
@@ -73,7 +73,7 @@
if [[ ! -f "$pghbaconf" ]]; then
echo "Error: The database is not initialized"
if [ "$1" != noadvice ]; then
- echo "Hint: Run 'cumin-database initialize'"
+ echo "Hint: Run 'cumin-database install'"
fi
return 1
fi
@@ -83,7 +83,7 @@
grep "$dbname" "$pghbaconf" &> /dev/null || {
echo "Error: The database is not configured"
if [ "$1" != noadvice ]; then
- echo "Hint: Run 'cumin-database configure'"
+ echo "Hint: Run 'cumin-database install'"
fi
return 1
}
@@ -93,7 +93,7 @@
psql -d cumin -U cumin -h localhost -c '\q' &> /dev/null || {
echo "Error: The database is not created"
if [ "$1" != noadvice ]; then
- echo "Hint: Run 'cumin-database create'"
+ echo "Hint: Run 'cumin-database install'"
fi
return 1
}
@@ -117,7 +117,7 @@
*)
echo "Error: The database is not created"
if [ "$1" != noadvice ]; then
- echo "Hint: Run 'cumin-database create'"
+ echo "Hint: Run 'cumin-database install'"
fi
return 1
;;
@@ -132,7 +132,7 @@
WARNING
This script installs a cumin database into the system postgresql
-instance.
+instance. It will take the following actions as necessary.
* It will stop and start the postgresql service.
Modified: branches/stability/cumin/bin/cumin-web
===================================================================
--- branches/stability/cumin/bin/cumin-web 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/bin/cumin-web 2012-05-31 14:49:15 UTC (rev 5395)
@@ -9,6 +9,8 @@
from cumin.config import *
from cumin.util import *
from parsley.loggingex import PipeLogThread
+from psycopg2 import OperationalError
+from cumin.admin import SchemaVersion, SchemaMissing
def restore_IO():
sys.stderr = sys.__stderr__
@@ -38,7 +40,61 @@
cumin.wallaby_refresh = values.wallaby_refresh
if cumin.wallaby_refresh == 0:
cumin.wallaby_refresh = None
-
+
+def set_authorize_config(cumin, values, access_root):
+
+ # Allow this to be turned on/off.
+ # Default will be off.
+ cumin.do_authorize = values.authorize
+
+ # This is the path to the xml file defining authorization.
+ # Allow this to be disabled via "None"
+ if values.auth_path == "None":
+ cumin.access_path = None
+ else:
+ # If there is no initial dir, prepend home
+ dir_name = os.path.split(values.auth_path)[0]
+ if len(dir_name) == 0:
+ cumin.access_path = os.path.join(access_root, values.auth_path)
+ else:
+ cumin.access_path = values.auth_path
+
+def set_ldap_configs(cumin, values):
+ cumin.ldap_timeout = values.ldap_timeout
+ cumin.ldap_tls_cacertdir = values.ldap_tls_cacertdir
+ cumin.ldap_tls_cacertfile = values.ldap_tls_cacertfile
+
+def check_cert_files(opts):
+ # If a certificate or key file has been specified,
+ # check to make sure that both exist and that the
+ # app has permission to read them.
+ if opts.server_cert or opts.server_key:
+ for attr in ("server_cert", "server_key"):
+ attr_name = attr.replace("_", "-")
+ f = getattr(opts, attr)
+ if f == "":
+ log.error("parameter %s must have a value" % \
+ attr_name)
+ return False
+
+ if not os.path.isfile(f):
+ log.error("%s is not a file." % f)
+ return False
+
+ try:
+ open(f, "r")
+ except Exception, e:
+ log.error("Exception reading %s, %s" % (attr_name, e))
+ return False
+ return True
+
+def adjust_return(passed_init, ret):
+ # Shift non-zer0 return codes left 1 bit
+ # and OR in whether or not init passed
+ if ret != 0:
+ ret = ret << 1 | passed_init
+ return ret
+
def main():
# Do our own simple option check so we can redirect IO early
# without worrying about other options or the behavior of optParse
@@ -72,6 +128,13 @@
print_exc()
pipeThread = None
+ # Use ArgError to jump to the finally block and exit
+ class ArgError(Exception):
+ pass
+
+ class CertificateError(Exception):
+ pass
+
try:
try:
return_code = 0
@@ -84,6 +147,8 @@
# Add additional parameters for web
parser.add_option("--host")
parser.add_option("--port", type=int)
+ parser.add_option("--server-cert")
+ parser.add_option("--server-key")
parser.add_option("--section", default="web")
parser.add_option("--daemon", action="store_true",
default=False)
@@ -106,15 +171,32 @@
values.log_max_archives)
broker_uris = [x.strip() for x in opts.brokers.split(",")]
+ authmech = [x.strip() for x in values.auth.split(";") if x !=
""]
cumin = Cumin(config.get_home(), broker_uris, opts.database,
- opts.host, opts.port, values.persona)
+ opts.host, opts.port,
+ opts.server_cert, opts.server_key,
+ values.persona, authmech)
+
if type(values.sasl_mech_list) == str:
cumin.sasl_mech_list = values.sasl_mech_list.upper()
+ if not check_cert_files(opts):
+ raise CertificateError
+
+ if len(args) != 0:
+ log.error("Extra arguments:" + "".join(["
"+arg for arg in args]))
+ raise ArgError
+
set_aviary_configs(cumin, values)
set_wallaby_configs(cumin, values, broker_uris)
+ set_ldap_configs(cumin, values)
+ # Someday we may let this be configurable, for now it will
+ # be hardwired
+ values.auth_path = "persona.xml"
+ set_authorize_config(cumin, values, config.get_access_root())
+
cumin.debug = opts.debug
cumin.user = values.user
cumin.update_interval = values.update_interval
Modified: branches/stability/cumin/etc/cumin.conf
===================================================================
--- branches/stability/cumin/etc/cumin.conf 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/etc/cumin.conf 2012-05-31 14:49:15 UTC (rev 5395)
@@ -30,11 +30,13 @@
# To use aviary job servers, uncomment the following line and edit as needed.
# The value is a comma separated list of URLs. The default port condor uses
# for an Aviary job server is 9090.
+# More details in 'Some parameter explanations by section' below.
# aviary-job-servers:
http://localhost:9090
# To use aviary query servers, uncomment the following line and edit as needed.
# The value is a comma separated list of URLs. The default port condor uses
# for an Aviary query server is 9091.
+# More details in 'Some parameter explanations by section' below.
# aviary-query-servers:
http://localhost:9091
# Full path to private key file used for ssl communication with aviary servers.
@@ -91,10 +93,17 @@
# log-file: $CUMIN_HOME/log/web.log
# host: localhost ('0.0.0.0' binds to all local interfaces)
# port: 45672
+# server-cert:
+# server-key:
# operator-email: [none]
# update-interval: 10
# max-qmf-table-sort: 1000
# persona: default
+# auth:
+# ldap_tls_cacertfile:
+# ldap_tls_cacertdir:
+# ldap_timeout: 30
+# authorize: False
[data]
# log-file: $CUMIN_HOME/log/data.log
@@ -172,107 +181,167 @@
# *************** Some parameter explanations by section ***************
-# [common]
+## [common]
-# wallaby-broker: [first item in 'brokers' list]
-# The broker to use for interaction with a Wallaby agent.
-# This is not necessarily the same broker used for grid
-# and messaging information. The default value is the
-# first item in the 'brokers' list if not specified. To
-# turn off interaction with Wallaby, set wallaby-broker to
-# the string 'None'.
+## sasl-mech-list:
+## A space separated list that restricts the SASL mechanisms
+## Cumin may use when authenticating to the broker. By default
+## Cumin will use ANONYMOUS authentication if the broker URL does
+## not contain user/password and will use PLAIN or DIGEST-MD5
+## if the broker URL does contain user/password. DIGEST-MD5 is
+## considered more secure than PLAIN by SASL and will be
+## used if is available. This value may be set explicitly
+## to make other choices.
+## Setting the value to 'AVAILABLE' will allow Cumin to use
+## any available mechanism including ANONYMOUS.
-# wallaby-refresh: 60
-# How often in seconds to contact the Wallaby agent
-# for updated information.
+## wallaby-broker: [first item in 'brokers' list]
+## The broker to use for interaction with a Wallaby agent.
+## This is not necessarily the same broker used for grid
+## and messaging information. The default value is the
+## first item in the 'brokers' list if not specified. To
+## turn off interaction with Wallaby, set wallaby-broker to
+## the string 'None'.
-# use-aviary: True
-# Whether or not to use the Aviary services for
-# remote procedure calls to condor. If this is
-# set to False, the QMF interface will be used
-# instead.
+## wallaby-refresh: 60
+## How often in seconds to contact the Wallaby agent
+## for updated information.
-# aviary-job-servers:
http://localhost:9090
-# Specifies the URIs for aviary job servers. The value
-# is a comma separated list of URIs. A full URI has the
-# form 'scheme://user/password@host:port/path". The scheme
-# will default to http if not specified, the port will
-# default to 9090, and the path will default to
-# /services/job/. User and password will be empty by
-# default. As a convenience, a URI that explicitly
-# sets a port number may be followed by one or more
-# port numbers separated by commas to specify
-# mulitple job servers whose URIs differ only
-# by port number.
+## aviary-job-servers:
http://localhost:9090
+## Specifies the URIs for aviary job servers. The value
+## is a comma separated list of URIs. A full URI has the
+## form 'scheme://user/password@host:port/path". The scheme
+## will default to http if not specified, the port will
+## default to 9090, and the path will default to
+## /services/job/. User and password will be empty by
+## default. As a convenience, a URI that explicitly
+## sets a port number may be followed by one or more
+## port numbers separated by commas to specify
+## mulitple job servers whose URIs differ only
+## by port number.
-# aviary-query-servers:
http://localhost:9091
-# Like aviary-job-servers but specifies URIs for aviary
-# query servers. The port value defaults to 9091 and the
-# path defaults to /services/query/. Other
-# defaults are as noted for aviary-job-servers.
+## aviary-query-servers:
http://localhost:9091
+## Like aviary-job-servers but specifies URIs for aviary
+## query servers. The port value defaults to 9091 and the
+## path defaults to /services/query/. Other
+## defaults are as noted for aviary-job-servers.
-# log-max-mb: 10
-# Maximum size in MB of *.log files created by cumin.
-# A log file reaching maximum size will be rolled over.
-# A value of 0 leaves the log file size unlimited.
+## log-max-mb: 10
+## Maximum size in MB of *.log files created by cumin.
+## A log file reaching maximum size will be rolled over.
+## A value of 0 leaves the log file size unlimited.
-# log-max-archives: 1
-# Number of rolled over log files to retain. A retained
-# log file will have a "." and a number added to its name.
-# A value of 0 will cause a log file to be truncated
-# rather than renamed if it reaches maximum size.
+## log-max-archives: 1
+## Number of rolled over log files to retain. A retained
+## log file will have a "." and a number added to its name.
+## A value of 0 will cause a log file to be truncated
+## rather than renamed if it reaches maximum size.
-# [web]
+## [web]
-# persona: default
-# Controls content of the top level page.
-# Valid values are "default", "grid", and "messaging".
-# The default persona contains content for both grid and
-# messaging.
+## server-cert:
+## This is a server certificate file in PEM format.
+## If server-cert and server-key are both set and valid,
+## Cumin will use ssl. The scheme in the Cumin URL will
+## be "https://" instead of "http://".
-# [data]
+## server-key:
+## This is a key file in PEM format that corresponds to
+## the server-cert. If server-cert and server-key are both
+## set and valid, Cumin will use ssl. The scheme in the
+## Cumin URL will be "https://" instead of "http://".
-# expire-enabled: True
-# Whether or not the instance runs a thread which periodically expires samples.
-# Running the expire thread from more than one instance is okay but unnecessary
+## persona: grid
+## Controls content of the top level page.
+## Valid values are "default", "grid", and "messaging".
+## The "default" persona contains content for both grid and
+## messaging.
-# expire-interval: 3600
-# How often the expire thread runs, in seconds
+## authorize: False
+## Controls whether or not roles are enforced.
+## Roles are assigned to users with cumin-admin.
+## If roles are enforced, users with only the 'user'
+## role will not see the 'Administrator' tab.
+## The default role for a new user is 'user'.
-# expire-threshold: 86400
-# The maximum age of a sample in seconds. If it's older, it will be deleted when
-# the expire thread runs.
+## auth:
+## Allows specification of LDAP servers for external authentication of
+## users during login. A server must support simple bind for authentication.
+## This is a semicolon separated list of URLs of the following form:
+##
+## ldap://host:port/dn??scope?filter
+##
+## Scope defaults to 'sub', filter defaults to 'uid=usnername'.
+## If a filter is specified Cumin will replace occurrences of %%s
+## with username. Example:
+##
ldap://alpha.exmple.com/ou=users,dc=example,dc=com;
+##
ldap://beta.example.com/ou=people,dc=example,dc=com??one?mail=%%s
-# vacuum-enabled: True
-# Whether or not the instance runs a thread which periodically vacuums the
-# database. Running the vacuum thread from more than one instance is okay but
-# unnecessary.
+## ldap_tls_cacertfile:
+## Setting this option turns on SSL communication to all LDAP servers.
+## Specifies a file containing certificates for all the Certificate
+## Authorities that Cumin will recognize. An LDAP server certificate
+## must be issued by a recognized Certificate Authority or Cumin will
+## refuse to communicate with the server.
+## If this option is set, Cumin will look at the specified file before
+## looking in the directory specified by ldap_tls_cacertdir.
-# vacuum-interval: 3600
-# How often the vacuum thread runs, in seconds
+## ldap_tls_cacertdir:
+## Setting this option turns on SSL communication to all LDAP servers.
+## Specifies a directory containing certificates for all the Certificate
+## Authorities that Cumin will recognize. Certificates may be in individual
+## files or in a certificate database created with certutil.
+## An LDAP server certificate must be issued by a recognized Certificate
+## Authority or Cumin will refuse to communicate with the server.
+## If ldap_tls_cacertfile is set, Cumin will look in that file before
+## looking in this directory.
-# include-classes: [all]
-# A comma separated list of QMF classes. The default value is all
-# classes in all packages. The cumin-data instance will be bound to
-# classes which appear in 'include-classes' but do not appear in
-# 'exclude-classes'. Classes are specified as package:class for a single
-# class or package:* for all classes in a package.
+## ldap_timeout: 30
+## Timeout in seconds for connections to an LDAP server
-# exclude-classes: [empty list]
-# A comma separated list of QMF classes. The default value is
-# an empty list. The cumin-data instance will be bound to
-# classes which appear in 'include-classes' but do not appear in
-# 'exclude-classes'. Classes are specified as package:class for a single
-# class or package:* for all classes in a package.
+## [data]
-# [master]
+## expire-enabled: True
+## Whether or not the instance runs a thread which periodically expires samples.
+## Running the expire thread from more than one instance is okay but unnecessary
-# webs: web
-# Comma separated list of sections in this file.
-# Each section specifies the configuration for a separate
-# instance of cumin-web.
+## expire-interval: 3600
+## How often the expire thread runs, in seconds
-# datas: data
-# Comma separated list of sections in this file.
-# Each section specifies the configuration for a separate
-# instance of cumin-data.
+## expire-threshold: 86400
+## The maximum age of a sample in seconds. If it's older, it will be deleted when
+## the expire thread runs.
+
+## vacuum-enabled: True
+## Whether or not the instance runs a thread which periodically vacuums the
+## database. Running the vacuum thread from more than one instance is okay but
+## unnecessary.
+
+## vacuum-interval: 3600
+## How often the vacuum thread runs, in seconds
+
+## include-classes: [all]
+## A comma separated list of QMF classes. The default value is all
+## classes in all packages. The cumin-data instance will be bound to
+## classes which appear in 'include-classes' but do not appear in
+## 'exclude-classes'. Classes are specified as package:class for a single
+## class or package:* for all classes in a package.
+
+## exclude-classes: [empty list]
+## A comma separated list of QMF classes. The default value is
+## an empty list. The cumin-data instance will be bound to
+## classes which appear in 'include-classes' but do not appear in
+## 'exclude-classes'. Classes are specified as package:class for a single
+## class or package:* for all classes in a package.
+
+## [master]
+
+## webs: web
+## Comma separated list of sections in this file.
+## Each section specifies the configuration for a separate
+## instance of cumin-web.
+
+## datas: data
+## Comma separated list of sections in this file.
+## Each section specifies the configuration for a separate
+## instance of cumin-data.
Modified: branches/stability/cumin/python/cumin/account/widgets.py
===================================================================
--- branches/stability/cumin/python/cumin/account/widgets.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/account/widgets.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -61,7 +61,6 @@
def render_content(self, sessino):
return "Change password"
-
class LoginPage(HtmlPage):
def __init__(self, app, name):
super(LoginPage, self).__init__(app, name)
@@ -119,28 +118,36 @@
self.validate(session)
if not self.errors.get(session):
- cursor = self.app.database.get_read_cursor()
+ user, ok = self.app.authenticator.authenticate(name, password)
+ if ok:
+ # You're in! almost...
+ # Check for a valid group if group authorization is on
+ roles = []
+ if self.app.authorizator.is_enforcing():
+ if user is None:
+ # This was an external user with no role
+ # entry in the Cumin database. We default the
+ # role to 'user'. In the future we may store
+ # roles externally as well.
+ roles = ['user']
+ else:
+ cursor = self.app.database.get_read_cursor()
+ roles = self.app.admin.get_roles_for_user(
+ cursor, user)
+ ok = self.app.authorizator.contains_valid_group(roles)
+ if not ok:
+ self.login_invalid.set(session, "roles")
- cls = self.app.model.com_redhat_cumin.User
- user = cls.get_object(cursor, name=name)
-
- if not user:
- self.login_invalid.set(session, True)
- return
-
- crypted = user.password
-
- if crypted and crypt(password, crypted) == crypted:
- # You're in!
-
- login = LoginSession(self.app, user)
- session.client_session.attributes["login_session"] = login
-
- url = self.page.origin.get(session)
-
- self.page.redirect.set(session, url)
+ # If we're still okay, set up the login
+ if ok:
+ if user is None:
+ user = name
+ login = LoginSession(self.app, user, roles)
+ session.client_session.attributes["login_session"] =
login
+ url = self.page.origin.get(session)
+ self.page.redirect.set(session, url)
else:
- self.login_invalid.set(session, True)
+ self.login_invalid.set(session, "credentials")
def render_operator_link(self, session):
email = self.app.operator_email
@@ -151,7 +158,10 @@
return "site operator"
def render_login_invalid(self, session):
- if self.login_invalid.get(session):
+ reason = self.login_invalid.get(session)
+ if reason == "roles":
+ return self.get_string("roles_invalid")
+ elif reason == "credentials":
return self.get_string("login_invalid")
class Submit(FormButton):
@@ -178,53 +188,52 @@
self.add_field(self.new1)
def validate(self, session):
- super(ChangePasswordForm, self).validate(session)
-
- current = self.current.get(session)
- new0 = self.new0.get(session)
- new1 = self.new1.get(session)
-
user = session.client_session.attributes["login_session"].user
-
# In case a different login session for this user has made
# changes, refresh the user object
+ if hasattr(user, "load"):
+ user.load(session.cursor)
+
+ new0 = self.new0.get(session)
+ new1 = self.new1.get(session)
- user.load(session.cursor)
-
- crypted = user.password
-
- if crypt_password(current, crypted) != crypted:
- error = FormError("The password is incorrect")
- self.errors.add(session, error)
-
if new0 != new1:
error = FormError("The new passwords do not match")
self.errors.add(session, error)
+ return
+ super(ChangePasswordForm, self).validate(session)
+
+ # authenticator.update_password does authentication
+ # before allowing the password change, no need to authenticate here
+
def process_submit(self, session):
self.validate(session)
if not self.errors.get(session):
- password = self.new0.get(session)
-
- conn = self.app.database.get_connection()
-
+ newpassword = self.new0.get(session)
+ oldpassword = self.current.get(session)
+ user = session.client_session.attributes["login_session"].user
try:
- cursor = conn.cursor()
+ status, message = self.app.authenticator.update_password(user.name,
+ oldpassword,
+ newpassword)
+ if not status:
+ if not message:
+ message = "Update failed."
+ error = FormError(message)
+ self.errors.add(session, error)
- user = session.client_session.attributes["login_session"].user
- user.password = crypt_password(password)
- user.save(cursor)
+ except Exception,e:
+ log.debug("Error in external authenticator: %s" , e )
+ error = FormError("Password change error. Please contact your site
administrator.")
+ self.errors.add(session,error)
- conn.commit()
- finally:
- conn.close()
-
+ if not self.errors.get(session):
task = ObjectTask(self.app)
invoc = task.start(session, None)
invoc.description = "Password changed"
invoc.end()
-
url = self.return_url.get(session)
self.page.redirect.set(session, url)
Modified: branches/stability/cumin/python/cumin/account/widgets.strings
===================================================================
--- branches/stability/cumin/python/cumin/account/widgets.strings 2012-05-31 12:18:57 UTC
(rev 5394)
+++ branches/stability/cumin/python/cumin/account/widgets.strings 2012-05-31 14:49:15 UTC
(rev 5395)
@@ -77,3 +77,7 @@
<p class="login_invalid">The user name and password you entered do not
match any account.</p>
+[LoginForm.roles_invalid]
+<p class="login_invalid">There are no valid roles
+configured for this account.</p>
+
Modified: branches/stability/cumin/python/cumin/admin.py
===================================================================
--- branches/stability/cumin/python/cumin/admin.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/admin.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -1,12 +1,20 @@
from StringIO import StringIO
from util import *
-from psycopg2 import IntegrityError
+from rosemary.sqlquery import SqlQuery, SqlInnerJoin
+from rosemary.sqlfilter import SqlComparisonFilter
+from psycopg2 import IntegrityError, ProgrammingError
log = logging.getLogger("cumin.admin")
schema_version = "1.1"
+class SchemaMissing(Exception):
+ pass
+
+class SchemaVersion(Exception):
+ pass
+
class CuminAdmin(object):
def __init__(self, app):
self.app = app
@@ -34,9 +42,13 @@
def get_schema_version(self, cursor):
cls = self.app.model.com_redhat_cumin.Info
- info = cls.get_object(cursor)
+ try:
+ info = cls.get_object(cursor)
+ except ProgrammingError:
+ info = None
+
if not info:
- raise Exception("The schema isn't there")
+ raise SchemaMissing("The schema isn't there")
return info.schema_version
def get_target_schema_version(self):
@@ -44,19 +56,28 @@
def check_schema(self, cursor):
cls = self.app.model.com_redhat_cumin.Info
- info = cls.get_object(cursor)
+ try:
+ info = cls.get_object(cursor)
+ except ProgrammingError:
+ info = None
if not info:
- raise Exception("The schema isn't there")
+ raise SchemaMissing("The schema isn't there")
if info.schema_version != schema_version:
args = (schema_version, info.schema_version)
msg = "Expected schema version %s; found version %s" % args
- raise Exception(msg)
+ raise SchemaVersion(msg)
return info.schema_version
def drop_schema(self, cursor):
+ cls = self.app.model.com_redhat_cumin.Info
+ try:
+ info = cls.get_object(cursor)
+ except ProgrammingError:
+ raise SchemaMissing("The schema isn't there")
+
writer = StringIO()
self.app.model.sql_model.write_drop_ddl(writer)
sql = writer.getvalue()
@@ -116,3 +137,26 @@
return mapping
+ def get_roles_for_user(self, cursor, user):
+ user_cls = self.app.model.com_redhat_cumin.User
+ role_cls = self.app.model.com_redhat_cumin.Role
+ mapping_cls = self.app.model.com_redhat_cumin.UserRoleMapping
+
+ query = SqlQuery(role_cls.sql_table)
+
+ SqlInnerJoin(query,
+ mapping_cls.sql_table,
+ mapping_cls.role.sql_column,
+ role_cls._id.sql_column)
+
+ SqlInnerJoin(query,
+ user_cls.sql_table,
+ user_cls._id.sql_column,
+ mapping_cls.user.sql_column)
+ #TODO: one column ?
+ cols = [ role_cls.name.sql_column ]
+ filt = SqlComparisonFilter(user_cls._id.sql_column, user._id, '=')
+ query.add_filter(filt)
+ sql = query.emit(cols)
+ cursor.execute(sql)
+ return [x[0] for x in cursor.fetchall()]
Copied: branches/stability/cumin/python/cumin/authenticator.py (from rev 4971,
trunk/cumin/python/cumin/authenticator.py)
===================================================================
--- branches/stability/cumin/python/cumin/authenticator.py (rev
0)
+++ branches/stability/cumin/python/cumin/authenticator.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -0,0 +1,558 @@
+from util import *
+try:
+ import ldap
+ import ldapurl
+ have_ldap = True
+except:
+ have_ldap = False
+
+import subprocess
+from subprocess import PIPE
+from psycopg2 import IntegrityError
+import syslog
+
+# Send sensitive authentication logs to syslog
+# but use this for other stuff. We want to keep
+# all user and password information out of the
+# publically readable log file.
+log = logging.getLogger("cumin.authenticator")
+
+
+class _syslog(object):
+ log_auth = False
+ @classmethod
+ def log(cls, msg):
+ if _syslog.log_auth:
+ syslog.syslog(msg)
+
+class CuminAuthenticatorLDAP(object):
+ def __init__(self, url, app):
+ self.valid = False
+ log.info("Initializing %s", self)
+ scheme, myurl = url.split("=",1)
+ err_msg = ""
+
+ self.tls_cacertdir = app.ldap_tls_cacertdir
+ self.tls_cacertfile = app.ldap_tls_cacertfile
+ self.ldap_timeout = app.ldap_timeout
+ has_cert = self.tls_cacertdir or self.tls_cacertfile
+
+ if has_cert and not ldap.TLS_AVAIL:
+ err_msg = "Authenticator: SSL is not supported by this python-ldap
module."
+
+ elif not has_cert and scheme == "ldaps":
+ err_msg = "Authenticator: scheme is ldaps but no tls_cacertdir
set."
+
+ else:
+ if self.tls_cacertdir:
+ if not os.path.isdir(self.tls_cacertdir):
+ err_msg = "Authenticator: '%s' is not a valid
directory." \
+ % self.tls_cacertdir
+ else:
+ try:
+ os.listdir(self.tls_cacertdir)
+ except Exception, e:
+ err_msg = "Authenticator, exception reading " \
+ "tls_cacertdir, %s" % e
+
+ if not err_msg and self.tls_cacertfile:
+ if not os.path.isfile(self.tls_cacertfile):
+ err_msg = "Authenticator: '%s' is not a valid
file." \
+ % self.tls_cacertfile
+ else:
+ try:
+ open(self.tls_cacertfile, "r")
+ except Exception, e:
+ err_msg = "Authenticator, exception reading " \
+ "tls_cacertfile, %s" % e
+ if not err_msg:
+ # If the scheme is just "ldap" but cert dir is set then we will
use
+ # the StartTLS mechanism for password binds
+ self.start_tls = has_cert and scheme == "ldap"
+ if scheme == "ldap":
+ log.info("Authenticator: using StartTLS for ldap: %s" % \
+ self.start_tls)
+ try:
+ self.url = ldapurl.LDAPUrl(myurl,
+ scope=ldapurl.LDAP_SCOPE_SUBTREE,
+ filterstr="uid=%s")
+ log.info("Authenticator: parsed LDAP url successfully, " \
+ "scheme is %s" % scheme)
+ self.valid = True
+ except Exception,e:
+ err_msg = "Authenticator: cannot parse LDAP url %s" % e
+
+ if err_msg:
+ log.error(err_msg)
+
+ def _set_ldap_options(self):
+ if self.tls_cacertdir:
+ ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.tls_cacertdir)
+
+ if self.tls_cacertfile:
+ ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.tls_cacertfile)
+
+ if self.tls_cacertfile or self.tls_cacertdir:
+ # Always demand a certificate if the cacert is set
+ ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
+
+ if self.ldap_timeout is not None:
+ ldap.set_option(ldap.OPT_TIMEOUT, self.ldap_timeout)
+
+ def _start_tls(self, conn):
+ if self.start_tls and not conn.start_tls_called:
+ conn.start_tls_called = True
+ log.debug("Authenticator: calling conn.start_tls_s() on ldap
connection")
+ try:
+ conn.start_tls_s()
+ except Exception, e:
+ log.error("Authenticator: StartTLS failed, %s" % e)
+ return False
+ return True
+
+ def __prep_con(self):
+ try:
+ self._set_ldap_options()
+ conn = ldap.initialize(self.url.urlscheme + "://"
+self.url.hostport)
+ conn.start_tls_called = False
+ except Exception,e:
+ log.error("Authenticator: cannot contact ldap server %s",e)
+ return False
+
+ if self.url.who and self.url.cred:
+ if not self._start_tls(conn):
+ return False
+ try:
+ conn.simple_bind_s(self.url.who, self.url.cred)
+ except:
+ log.error("Authenticator: cannot authenticate as service %s",
+ self.url.who)
+ return False
+ return conn
+
+ def _gen_filter(self, filterstr, value):
+ count = filterstr.count("%s")
+ if count > 0:
+ vals = (value,) * count
+ return filterstr % vals
+ return filterstr
+
+ def _find_user(self, user):
+ res = False
+ conn = self.__prep_con()
+ if conn:
+ filter = self._gen_filter(self.url.filterstr, user)
+ try:
+ res = conn.search_s(self.url.dn, self.url.scope, filter)
+ except Exception, e:
+ msg = str(e)
+ if not msg:
+ msg = e.__repr__()
+ log.error("Authenticator: find_user, "\
+ "query returned exception %s" % msg)
+ return conn, res
+
+ def find_user(self, user):
+ conn, res = self._find_user(user)
+ if res:
+ return True
+ return False
+
+ def change_pw(self, user, oldpass, newpass):
+ conn, res = self._find_user(user)
+ if not res:
+ log.info("Authenticator: update password, "\
+ "query returned no results in %s",
self.__class__.__name__)
+ else:
+ res = False
+ if self._start_tls(conn):
+ dn,ent = res[0]
+ conn.simple_bind_s(dn,oldpass)
+ conn.passwd_s(dn,oldpass,newpass)
+ log.info("Authenticator: update password succeeded in %s",
+ self.__class__.__name__)
+ _syslog.log("cumin: updated password via LDAP for user %s" %
dn)
+ res = True
+ return res
+
+ def batch_import(self):
+ log.debug("Authenticator: batch import in %s",
self.__class__.__name__)
+ userlist = res = []
+ conn = self.__prep_con()
+ error = not conn
+ if not error:
+ filter = self._gen_filter(self.url.filterstr, "*")
+ try:
+ res = conn.search_s(self.url.dn, self.url.scope, filter)
+ except Exception, e:
+ log.error("Authenticator:, batch import, "\
+ "query returned exception %s", e)
+ error = True
+ if not res:
+ log.info("Authenticator: batch import failed, "\
+ "query returned no results in %s",
self.__class__.__name__)
+ else:
+ for dn,ent in res:
+ dn_arr = ldap.dn.explode_rdn(dn)
+ naming_attr = dn_arr[0].split('=')[0]
+ userlist.append(ent[naming_attr][0])
+ return userlist, error
+
+ def authenticate(self, username, password):
+ log.debug("Authenticating against %s", self.__class__.__name__)
+ conn, res = self._find_user(username)
+ if not res:
+ log.info("Authenticator: authentication failed, "\
+ "query returned no results in %s",
self.__class__.__name__)
+ else:
+ # We need to check for zero length passwords here and disallow.
+ # This is because an LDAP directory server MAY allow unauthenticated
+ # binds, which are indicated by a zero length password. Red Hat Directory
+ # Services turns unauthenticated binds off by default, for example,
+ # but who knows how this particular server may be configured?
+
+ # If unauthenticated binds are allowed by the server, then simple_bind_s
+ # below will succeed with a zero lengh password and cumin will think that
+ # password authentication has succeeded when it really hasn't.
+ if password == "":
+ log.error("Authenticator: zero length password is not allowed for
"\
+ "ldap users")
+ return False
+
+ if self._start_tls(conn):
+ for dn,ent in res:
+ try:
+ conn.simple_bind_s(dn,password)
+ _syslog.log("cumin: authenticated user %s via LDAP" %
dn)
+ return True
+ except Exception, e:
+ log.info("Authenticator: LDAP authentication "\
+ "returned exception %s" % e)
+ return False
+
+class CuminAuthenticatorScript(object):
+ def __init__(self, commandline, *args):
+ self.valid = False
+ self.params = {}
+ self.cap = []
+ me = self.__class__.__name__
+ for a in commandline.split(','):
+ # Allow capability with a null value
+ args = a.split('=')
+ if len(args) == 1:
+ args.append("")
+ param,val = args
+ self.params[param] = val
+
+ if self.params.has_key('script') and \
+ os.access(self.params['script'], os.X_OK):
+
+ # Allow default with all operations and no specific
+ # parameters per operation. The convention here is
+ # to store the first letter in self.cap, but anything
+ # unambiguous would do.
+ if len(self.params) == 1:
+ self.cap = ['a','c','l','f']
+ log.debug("Authenticator: adding all capabilities to %s", me)
+ else:
+ msg = "Authenticator: adding %s capability to %s"
+ for cap in ("auth", "change", "list",
"find"):
+ if self.params.has_key(cap):
+ log.debug(msg % (cap, me))
+ self.cap.append(cap[0])
+ self.valid = True
+ else:
+ log.error('Authenticator: script parameter missing or target file '\
+ 'not executable in %s', me)
+
+ def _gen_input(self, op, *args):
+ if op in self.params and len(self.params[op]) > 0:
+ args = (self.params[op],)+args
+ return ",".join(["%s" % x for x in args])
+
+ def authenticate(self, username, password):
+ me = self.__class__.__name__
+ if 'a' in self.cap:
+ log.debug("Authenticator: authenticate in %s" % me)
+ # Use nameless pipes to pass parameters.
+ # If they are passed as arguments they show up in the output
+ # of ps, etc. This way is secure.
+ # Note, the call to communicate causes an EOF on stdin
+ try:
+ cmd = [self.params['script']]
+ args = self._gen_input("auth", username, password)
+ res = subprocess.Popen(cmd,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ out, err = res.communicate(input=args)
+ log.debug("Authenticator: authenticate, script returned "\
+ "%s, %s, exitcode %s", out, err, res.returncode)
+ except:
+ log.error("Authenticator: authenticate, "\
+ "exception running script, return False")
+ return False
+
+ if res.returncode == 0:
+ _syslog.log("cumin: authenticated user %s via script" %
username)
+ return True
+ else:
+ log.debug("Authenticator: 'auth' not supported by script")
+ return False
+
+ def find_user(self, username):
+ me = self.__class__.__name__
+ if 'f' in self.cap:
+ log.debug("Authenticator: executing find_user in %s" % me)
+ # Use nameless pipes to pass parameters.
+ # If they are passed as arguments they show up in the output
+ # of ps, etc. This way is secure.
+ # Note, the call to communicate causes an EOF on stdin
+ try:
+ cmd = [self.params['script']]
+ args = self._gen_input("find", username)
+ res = subprocess.Popen(cmd,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ out, err = res.communicate(input=args)
+ log.debug("Authenticator: find_user, script returned "\
+ "%s, %s, exitcode %s", out, err, res.returncode)
+ except:
+ log.error("Authenticator: find_user, "\
+ "exception running script, return False")
+ return False
+
+ if res.returncode == 0:
+ return True
+ else:
+ log.debug("Authenticator: 'find' not supported by script")
+ return False
+
+ def batch_import(self):
+ if 'l' in self.cap:
+ log.debug("Authenticator: batch import in %s",
+ self.__class__.__name__)
+ # Use nameless pipes to pass parameters.
+ # If they are passed as arguments they show up in the output
+ # of ps, etc. This way is secure.
+ # Note, the call to communicate causes an EOF on stdin
+ try:
+ cmd = [self.params['script']]
+ args = self._gen_input("list")
+ res = subprocess.Popen(cmd,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ out, err = res.communicate(input=args)
+ log.debug("Authenticator: batch import, script returned "\
+ "%s, %s, exitcode %s", out, err, res.returncode)
+ except:
+ log.error("Authenticator: batch_import, "\
+ "exception running script, return False")
+ return []. False
+
+ return out.split(','), False
+ else:
+ log.debug("Authenticator: 'list' not supported by script")
+ return [], False
+
+ def change_pw(self, user, newpass, oldpass):
+ if 'c' in self.cap:
+ me = self.__class__.__name__
+ log.debug("Authenticator: change password in %s" % me)
+
+ # Use nameless pipes to pass parameters.
+ # If they are passed as arguments they show up in the output
+ # of ps, etc. This way is secure.
+ # Note, the call to communicate causes an EOF on stdin
+ try:
+ cmd = [self.params['script']]
+ args = self._gen_input("change", user, newpass, oldpass)
+ res = subprocess.Popen(cmd,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ out, err = res.communicate(args)
+ log.debug("Authenticator: change password, script returned "\
+ "%s, %s, exitcode %s", out, err, res.returncode)
+ except:
+ log.error("Authenticator: change_pw, "\
+ "exception running script, return False")
+ return False
+
+ if res.returncode == 0:
+ _syslog.log("cumin: change password via script for user %s" %
user)
+ return True
+ else:
+ log.debug("Authenticator: 'change' not supported by
script")
+ return False
+
+if have_ldap:
+ authen_map = {'ldap': CuminAuthenticatorLDAP,
+ 'ldaps': CuminAuthenticatorLDAP,
+ 'script': CuminAuthenticatorScript}
+else:
+ authen_map = {'script': CuminAuthenticatorScript}
+
+class CuminAuthenticator(object):
+ def __init__(self, app, log_authentication=True):
+ self.authenticators=[]
+ self.app = app
+ _syslog.log_auth = log_authentication
+ log.info("Initializing %s", self)
+ if not have_ldap:
+ log.info("Authenticator: import of ldap modules failed, "\
+ "ldap mechanism not available")
+ for authmech in app.authmech:
+ # let the 'internal' word be used here since it has been
+ # on the trunk for a while. It means nothing, since the
+ # internal database will always be consulted first.
+ if authmech not in ("internal", ""):
+ # Allow initial ldap= to be skipped
+ try:
+ if authmech.startswith("ldap://"):
+ mechname = "ldap"
+ authmech = "ldap=" + authmech
+ elif authmech.startswith("ldaps://"):
+ mechname = "ldaps"
+ authmech = "ldaps=" + authmech
+ else:
+ mechname = authmech.split('=',1)[0]
+ classname = authen_map[mechname]
+ except KeyError:
+ log.error("Authenticator: unsupported authenticator type
%s",
+ mechname)
+ continue
+
+ c = classname(authmech, self.app)
+ if c.valid:
+ self.authenticators.append(c)
+ log.info("Authenticator: adding auth mechanism %s %s" \
+ % (mechname, c.valid and "succeeded" or
"failed"))
+
+ def batch_import(self, backend, output_on=False):
+ numusers = 0
+ numconflicts = 0
+ errors = False
+ import_list = []
+ log.debug("Authenticator: calling batch import")
+ try:
+ classname = authen_map[backend]
+ except KeyError:
+ log.error("Authenticator: batch import, unsupported authenticator
"\
+ "type '%s'", backend)
+ errors = True
+
+ if not errors:
+ try:
+ idx = [x.__class__.__name__ for x in self.authenticators]
+ idx = idx.index(classname.__name__)
+ except ValueError:
+ log.error("Authenticator: batch import, authenticator "\
+ "not configured '%s'", backend)
+ errors = True
+
+ if not errors:
+ users, errors = self.authenticators[idx].batch_import()
+ for imp in users:
+ import_list.append(imp)
+
+ if len(import_list) > 0:
+ try:
+ conn = self.app.database.get_connection()
+ cursor = conn.cursor()
+ role = self.app.admin.get_role(cursor, "user")
+ for importuser in import_list:
+ try:
+ user = self.app.admin.add_user(cursor, importuser, "")
+ if output_on:
+ print("Importing user %s" % importuser)
+ self.app.admin.add_assignment(cursor, user, role)
+ conn.commit()
+ numusers += 1
+ except IntegrityError:
+ if output_on:
+ print("Import failed, a user called '%s'
"\
+ "already exists" % importuser)
+ numconflicts += 1
+ conn.rollback()
+ finally:
+ conn.close()
+ return numusers, numconflicts, errors
+
+ def authenticate(self, username, password):
+ cursor = self.app.database.get_read_cursor()
+ cls = self.app.model.com_redhat_cumin.User
+ user = cls.get_object(cursor, name=username)
+ if user is None or len(user.password) == 0:
+ res = False
+ log.debug("Authenticator: authenticating external user")
+ if len(self.authenticators) == 0:
+ log.debug("Authenticator: no external mechanisms available")
+ for authenticator in self.authenticators:
+ log.debug("Authenticator: authenticate, try %s",
authenticator)
+ res = authenticator.authenticate(username, password)
+ if res:
+ break
+ log.debug("Authenticator: authentication %s for external user" \
+ % (res and "succeeded" or "failed"))
+ msg = "cumin: authentication %s for external user %s" \
+ % (res and "succeeded" or "failed", username)
+ else:
+ res = crypt(password, user.password) == user.password
+ log.debug("Authenticator: authentication %s for user" \
+ % (res and "succeeded" or "failed"))
+ msg = "cumin: authentication %s for user %s" \
+ % (res and "succeeded" or "failed", username)
+ _syslog.log(msg)
+ return user, res
+
+ def find_user(self, username):
+ # Like authenticate, but find_user only confirms the
+ # existence of a user. We are not concerned with password here.
+ log.debug("Authenticator: calling find_user")
+ cursor = self.app.database.get_read_cursor()
+ cls = self.app.model.com_redhat_cumin.User
+ user = cls.get_object(cursor, name=username)
+ res = user and len(user.password) > 0
+ if not res:
+ # Try to find the user externally
+ log.debug("Authenticator: finding external user")
+ if len(self.authenticators) == 0:
+ log.debug("Authenticator: no external mechanisms available")
+ for authenticator in self.authenticators:
+ log.debug("Authenticator: find_user, try %s", authenticator)
+ res = authenticator.find_user(username)
+ if res:
+ break
+ return user, res
+
+ def update_password(self, username, oldpassword, newpassword):
+ status = False
+ message = ""
+ log.debug("Authenticator: calling update_password")
+ conn = self.app.database.get_connection()
+ try:
+ cursor = conn.cursor()
+ cls = self.app.model.com_redhat_cumin.User
+ user = cls.get_object(cursor, name=username)
+ if user is None or user.password == "":
+ # Disable change password for external users
+ message = "Change password is not allowed for external users"
+
+ #for authenticator in self.authenticators:
+ # status = authenticator.authenticate(username, oldpassword)
+ # if status:
+ # status = authenticator.change_pw(username,
+ # oldpassword, newpassword)
+ # if not status:
+ # message = "Could not change password"
+ # break
+ #if not status and message == "":
+ # message = "The password is incorrect"
+
+ else:
+ status = crypt(oldpassword, user.password) == user.password
+ if status:
+ user.password = crypt_password(newpassword)
+ user.save(cursor)
+ conn.commit()
+ _syslog.log("cumin: updated password for user %s" %
username)
+ else:
+ message = "The password is incorrect"
+ finally:
+ conn.close()
+ return status, message
Modified: branches/stability/cumin/python/cumin/config.py
===================================================================
--- branches/stability/cumin/python/cumin/config.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/config.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -51,10 +51,15 @@
param = ConfigParameter(web, "persona", str)
param.default = "default"
-
+
param = ConfigParameter(web, "fast-view-attributes", str)
param.default =
"JobStatus,Cmd,Args,ExitStatus,JobStartDate,LastRemoteHost,LastJobStatus,Owner"
+ # Turn this into a boolean, default is off for introduction.
+ # Hardwire the path to persona.xml unless/until we allow overriding
+ param = ConfigParameter(web, "authorize", bool)
+ param.default = False
+
def create_data_section(self, name, strict_section):
data = CuminConfigSection(self, name, strict_section)
data.log_file.default = os.path.join(self.home, "log", name +
".log")
@@ -94,6 +99,9 @@
def get_home(self):
return self.home
+ def get_access_root(self):
+ return os.path.join(self.home, "model/access/")
+
def parse(self):
paths = list()
@@ -145,6 +153,20 @@
param = ConfigParameter(self, "brokers", str)
param.default = "amqp://localhost"
+ # Put this here, because authentication is something that
+ # might need to be done commonly
+ param = ConfigParameter(self, "auth", str)
+ param.default = ""
+
+ param = ConfigParameter(self, "ldap_tls_cacertfile", str)
+ param.default = ""
+
+ param = ConfigParameter(self, "ldap_tls_cacertdir", str)
+ param.default = ""
+
+ param = ConfigParameter(self, "ldap_timeout", int)
+ param.default = 30
+
# Leave default set to None, which is equivalent to
# previous behavior
param = ConfigParameter(self, "sasl-mech-list", str)
@@ -161,6 +183,12 @@
param = ConfigParameter(self, "aviary-query-servers", str)
param.default = ""
+ param = ConfigParameter(self, "server-cert", str)
+ param.default = ""
+
+ param = ConfigParameter(self, "server-key", str)
+ param.default = ""
+
param = ConfigParameter(self, "aviary-key", str)
param.default = ""
Modified: branches/stability/cumin/python/cumin/database.py
===================================================================
--- branches/stability/cumin/python/cumin/database.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/database.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -16,11 +16,37 @@
self.connection_args = dict()
self.thread_local = threading.local()
- def init(self):
+ def init(self, schema_version_check):
log.info("Initializing %s", self)
#m = re.match(r"^([^:]+)://([^@]+)@([^/]+)/(.+)$", self.uri)
+ conn = None
+ try:
+ conn = self.get_connection()
+ cursor = conn.cursor()
+ self.check_connection(cursor)
+ if schema_version_check:
+ self.app.admin.check_schema(cursor)
+ conn.close()
+ except psycopg2.OperationalError:
+ conn and conn.close()
+ log.error("Connection to database failed, is the database
running?")
+ raise
+ except Exception, e:
+ conn and conn.close()
+ log.error(str(e))
+ raise
+ def check(self):
+ # checks moved to init because the model has to be
+ # initialized before we can check the schema. And
+ # there is no point connecting to the database twice...
+ log.info("Checking %s", self)
+
+ def check_connection(self, cursor):
+ cursor.execute("select now()")
+ log.debug("Database is talking at '%s'", self.dsn)
+
def get_connection(self):
return psycopg2.connect(self.dsn)
Modified: branches/stability/cumin/python/cumin/grid/dashboard.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/dashboard.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/grid/dashboard.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -15,7 +15,7 @@
from cumin.objectselector import ObjectSelector, MonitorSelfStatColumn,
ObjectTableColumn, ObjectTable,\
ObjectLinkColumn, CsvStatsExporter, ExportButton, MonitorSelfAgeColumn
from cumin.parameters import YoungestAttribute
-from cumin.util import rgb_to_string
+from cumin.util import rgb_to_string, xml_escape
strings = StringCatalog(__file__)
log = logging.getLogger("cumin.dashboard")
@@ -539,10 +539,11 @@
def get_item_unit(self, session, item):
return item.unit
- def render_formatted_value(self, session, item, value):
+ def render_formatted_value(self, session, item, value, escape=False):
if item is self.PERCENT_COLUMN:
return "%0.02f%%" % value
- return super(DashboardCapacitySlotSummary, self).render_formatted_value(session,
item, value)
+ return super(DashboardCapacitySlotSummary,
+ self).render_formatted_value(session, item, value, escape)
def render_html_title(self, session, item):
if item is self.PERCENT_COLUMN:
@@ -689,7 +690,7 @@
return record[1]
def render_item_value(self, session, item):
- return self.get_item_value(session, item)
+ return xml_escape(self.get_item_value(session, item))
def render_html_title(self, session, item):
(_, (name, value)) = item
Modified: branches/stability/cumin/python/cumin/grid/job.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/job.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/grid/job.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -14,8 +14,9 @@
from cumin.formats import fmt_datetime, fmt_link
from wooly import Widget, Parameter, Attribute
-from wooly.util import StringCatalog, Writer, escape_amp, escape_entity
-from wooly.forms import Form, FormButton, StringField
+from wooly.util import StringCatalog, Writer, escape_amp, escape_entity,\
+ xml_escape
+from wooly.forms import Form, FormButton, StringField, NoXMLStringField
from wooly.widgets import ModeSet, PropertySet, TemplateRenderer
from wooly.template import WidgetTemplate
from wooly.parameters import ListParameter, IntegerParameter, DictParameter
@@ -313,7 +314,7 @@
def get_item_content(self, session, item):
# item here is the unicode job id
- return item
+ return xml_escape(item)
class JobSelectionHold(JobObjectSelectorTask):
def __init__(self, app, selector, verb):
@@ -1109,7 +1110,7 @@
self.task.invoke(session, scheduler, job_id, reason, submission)
self.task.exit_with_redirect(session)
-class ReasonField(StringField):
+class ReasonField(NoXMLStringField):
def render_title(self, session):
return "Reason"
Modified: branches/stability/cumin/python/cumin/grid/limit.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/limit.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/grid/limit.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -79,7 +79,11 @@
def render_text_align(self, session):
return "right"
- class MaxColumn(ObjectTableColumn):
+ class MaxColumn(ObjectTableColumn):
+ def __init__(self, app, name, attr):
+ super(LimitTable.MaxColumn, self).__init__(app, name, attr)
+ self.do_escape = False
+
def render_text_align(self, session):
return "right"
Modified: branches/stability/cumin/python/cumin/grid/main.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/main.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/grid/main.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -26,6 +26,8 @@
super(Module, self).init()
self.frame = PoolFrame(self.app, "grid")
+ self.frame.cumin_module = self.name
+
self.app.main_page.main.grid = self.frame
self.app.main_page.main.add_tab(self.frame)
Modified: branches/stability/cumin/python/cumin/grid/negotiator.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/negotiator.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/grid/negotiator.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -186,8 +186,11 @@
info = self.get_config_for_groups(session, config, [group])
return info[group][config]
except:
- return "<em>loading</em>"
+ return self.loading_indicator()
+ def loading_indicator(self):
+ return "<em>loading</em>"
+
def get_unclaimed_dyn_quota(self, session, groups):
info = self.get_config_info(session)
total = 0.0
Modified: branches/stability/cumin/python/cumin/grid/pool.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/pool.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/grid/pool.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -78,6 +78,7 @@
self.tag = TagsFrame(app, "tag")
self.add_mode(self.tag)
+ self.tag.cumin_module = "configuration"
dashboard = PoolDashboard(app, "dashboard", self.object)
self.view.add_tab(dashboard)
Modified: branches/stability/cumin/python/cumin/grid/quota.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/quota.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/grid/quota.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -7,6 +7,7 @@
ObjectQmfTable
from cumin.qmfadapter import ObjectQmfAdapter
from cumin.widgets import StaticColumnHeader
+from cumin.util import xml_escape
from wooly.template import WidgetTemplate
from wooly.util import StringCatalog, Writer
@@ -29,6 +30,7 @@
self.negotiator = negotiator
self.task = task
self.user_task = None
+ self.do_escape = False
def render_title(self, session, *args):
return self.title
@@ -176,6 +178,7 @@
self.expand = expand
self.group_helper = group_helper
+ self.do_escape = False
def get_class_list(self, session):
return ["expand"]
@@ -225,6 +228,10 @@
self.header = StaticColumnHeader(app, "header")
self.replace_child(self.header)
+ # Turn off escape in the parent because although we
+ # have to escape here, it's based on context
+ self.do_escape = False
+
def render_cell_content(self, session, group):
if session.page == self.app.export_page:
return group
@@ -234,16 +241,17 @@
parts = group.split('.')
indent = len(parts) - 1
user = parts[indent]
- return "<span style='padding-left:
%dem;'>%s</span>" % (indent, user)
+ return "<span style='padding-left:
%dem;'>%s</span>" %\
+ (indent, xml_escape(user))
- return group
+ return xml_escape(group)
class DynamicColumn(QmfGroupColumn):
def __init__(self, app, name, attr, getter, negotiator, task, group_helper):
super(QuotaSelector.DynamicColumn, self).__init__(app, name, attr, getter,
negotiator, task)
self.group_helper = group_helper
-
+
def render_cell_content(self, session, group):
value = self.group_helper.get_config_value(session, group,
"GROUP_QUOTA_DYNAMIC")
self.task.form.group_leader.set(session, group)
@@ -251,11 +259,13 @@
try:
fval = float(value)
except ValueError:
- return value
+ if value == self.group_helper.loading_indicator():
+ return value
+ return xml_escape(value)
except:
if isinstance(value, Exception):
content = "<span
class='QuotaError'>error</span>"
- val = len(value.args) > 0 and value.args[0] or ""
+ val = len(value.args) > 0 and xml_escape(value.args[0]) or
""
if session.page == self.app.export_page:
return val
return fmt_link("#", content, "", "",
val)
@@ -282,18 +292,19 @@
try:
fval = float(value)
except ValueError:
- return value
+ if value == self.group_helper.loading_indicator():
+ return value
+ return xml_escape(value)
except:
if isinstance(value, Exception):
content = "<span
class='QuotaError'>error</span>"
- val = len(value.args) > 0 and value.args[0] or ""
+ val = len(value.args) > 0 and xml_escape(value.args[0]) or
""
if session.page == self.app.export_page:
return val
return fmt_link("#", content, "", "",
val)
if session.page == self.app.export_page:
return value
-
content = "%s" % str(round(fval, 2))
return content
Modified: branches/stability/cumin/python/cumin/grid/scheduler.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/scheduler.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/grid/scheduler.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -123,7 +123,7 @@
"MonitorSelfImageSize","MonitorSelfRegisteredSocketCount",
"MonitorSelfResidentSetSize","MonitorSelfTime")
- def render_formatted_value(self, session, item, value):
+ def render_formatted_value(self, session, item, value, escape=False):
def fmt_b(value):
return value is not None and fmt_bytes(value) or None
@@ -147,14 +147,14 @@
elif format == "KiB":
return fmt_kb(value)
name = self.get_item_name(session, item)
- # Since condor.xml doesn't have a unit for this, we will improvise a
bit.
- # In a perfect world, this might use the same Rosemary code that Negotiator
uses
+ # Since condor.xml doesn't have a unit for this, we improvise a bit.
+ # This could perhaps use the same Rosemary code as Negotiator.
if(name in ["MonitorSelfAge"]):
return fmt_timestamp_ddhhmmss(value)
except:
pass
- return CuminStatistic.fmt_value(value)
+ return CuminStatistic.fmt_value(value, escape)
def get_item_name(self, session, item):
the_item, _ = item
Modified: branches/stability/cumin/python/cumin/grid/submission.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/submission.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/grid/submission.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -10,13 +10,14 @@
ObjectTableColumn, ObjectTable
from cumin.task import TaskLink, Task, ObjectTaskForm
from cumin.parameters import ObjectAttribute
+from cumin.util import xml_escape
from rosemary.sqlfilter import SqlFilter
from rosemary.sqlquery import SqlInnerJoin
from wooly import Attribute, Parameter
from wooly.forms import StringField, ScalarField, FormError, OptionInputSet,\
- MultilineStringField, FormField, IntegerField
+ MultilineStringField, FormField, IntegerField, NoXMLStringField
from wooly.parameters import IntegerParameter
log = logging.getLogger("cumin.grid.submission")
@@ -60,7 +61,7 @@
def render_auxtitle(self, session):
sch = self.get_scheduler(session)
- return "Scheduler '%s'" % sch.Name
+ return "Scheduler '%s'" % xml_escape(sch.Name)
class SubmissionData(ObjectSqlAdapter):
def __init__(self, app):
@@ -203,7 +204,7 @@
self.columns.append(scheduler._id.sql_column)
self.columns.append(scheduler.Name.sql_column)
-class JobDescriptionField(StringField):
+class JobDescriptionField(NoXMLStringField):
def __init__(self, app, name):
super(JobDescriptionField, self).__init__(app, name)
@@ -273,7 +274,7 @@
return item._id
def render_item_content(self, session, item):
- return item.Name
+ return xml_escape(item.Name)
def render_item_selected_attr(self, session, item):
if item._id == self.param.get(session):
Modified: branches/stability/cumin/python/cumin/grid/tags.py
===================================================================
--- branches/stability/cumin/python/cumin/grid/tags.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/grid/tags.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -18,6 +18,7 @@
from cumin.task import TaskLink, Task, ObjectTaskForm
from cumin.widgets import *
from cumin.stat import *
+from cumin.util import xml_escape
from sage.wallaby.wallabyoperations import WallabyOperations, WBTypes
from sage.util import *
@@ -103,7 +104,10 @@
wallaby_features = self.app.wallaby.get_data(WBTypes.FEATURES)
for i, tag in enumerate(wallaby_tags):
- data.append({'Tag':str(escape_entity(tag.name)),
'Features':", ".join(tag.features),
'NumHosts':len(self.app.wallaby.get_node_names(tag)),
'Host':self.app.wallaby.get_node_names(tag)} )
+ data.append({'Tag':str(escape_entity(tag.name)),
+ 'Features':", ".join(tag.features),
+
'NumHosts':len(self.app.wallaby.get_node_names(tag)),
+ 'Host':self.app.wallaby.get_node_names(tag)} )
except Exception, e:
log.exception(e)
@@ -117,7 +121,10 @@
if results:
for result in results:
#Tag goes in the 0 and 1 column, 0 for checkbox value, 1 for the column
value
- records.append([result['Tag'], result['Tag'],
result['Features'], result['NumHosts'], result['Host']])
+ records.append([result['Tag'],
+ result['Tag'],
+ result['Features'],
+ result['NumHosts'], result['Host']])
return records
@@ -139,7 +146,9 @@
wallaby_nodes = self.app.wallaby.get_data(WBTypes.NODES)
data = []
for i, node in enumerate(wallaby_nodes):
- data.append({'Host':node.name,
'Tags':self.app.wallaby.get_tag_names(node),
'Checkin':node.last_checkin})
+ data.append({'Host':node.name,
+ 'Tags':self.app.wallaby.get_tag_names(node),
+ 'Checkin':node.last_checkin})
return data
@@ -161,7 +170,7 @@
def render_title(self, session):
obj = self.object.get(session)
if obj is not None:
- return obj.name
+ return xml_escape(obj.name)
else:
return ""
@@ -243,7 +252,7 @@
retval = ""
try:
obj = self.tag.get(session)
- retval = ", ".join(self.app.wallaby.get_node_names(obj))
+ retval = xml_escape(",
".join(self.app.wallaby.get_node_names(obj)))
except Exception, e:
log.debug("Exception in rendering tag hosts, tags probably not loaded
yet: %s", e.message)
return retval
@@ -252,7 +261,7 @@
retval = ""
try:
obj = self.tag.get(session)
- retval = ", ".join(obj.features)
+ retval = xml_escape(", ".join(obj.features))
except Exception, e:
log.debug("Exception in rendering tag features, tags probably not loaded
yet: %s", e.message)
return retval
@@ -278,7 +287,7 @@
def get_title(self, session):
retval = ""
try:
- retval = "Tag '%s'" % self.object.get(session).name
+ retval = "Tag '%s'" %
xml_escape(self.object.get(session).name)
except Exception, e:
pass
return retval
@@ -338,10 +347,10 @@
def render_cell_content(self, session, data):
feature_list = data[2]
features = truncate_text(feature_list, 70, True)
- return features
+ return features
def render_cell_title(self, session, data):
- return data[2]
+ return data[2]
class HostCountColumn(ObjectTableColumn):
def render_cell_content(self, session, data):
@@ -861,7 +870,7 @@
if not value or value =="":
value = "No hosts currently selected"
value = truncate_text(value, 50, True)
- return value
+ return xml_escape(value)
def render_title(self, session):
return "Current hosts"
@@ -980,7 +989,7 @@
value = ""
tag_obj = self.app.wallaby.get_tag_by_name(tag)
if tag_obj is not None:
- value = ", ".join(tag_obj.features)
+ value = xml_escape(", ".join(tag_obj.features))
if not value or value == "":
value = "No features currently selected"
value = truncate_text(value, 50, True)
@@ -1265,7 +1274,7 @@
log.exception(e)
def get_item_content(self, session, item):
- return item
+ return xml_escape(item)
def fetchItems(self, session, type):
'''
@@ -1275,7 +1284,7 @@
item_list = list()
for item in wallaby_items:
- item_list.append(str(item.name))
+ item_list.append(xml_escape(str(item.name)))
item_list.sort()
return item_list
\ No newline at end of file
Modified: branches/stability/cumin/python/cumin/inventory/main.py
===================================================================
--- branches/stability/cumin/python/cumin/inventory/main.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/inventory/main.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -16,6 +16,7 @@
self.frame = InventoryFrame(app, "inventory")
self.app = app
+ self.frame.cumin_module = name
def init(self):
self.app.main_page.main.inventory = self.frame
Modified: branches/stability/cumin/python/cumin/inventory/system.py
===================================================================
--- branches/stability/cumin/python/cumin/inventory/system.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/inventory/system.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -14,7 +14,7 @@
from wooly import Session, Widget
from wooly.datatable import *
-from wooly.util import StringCatalog
+from wooly.util import StringCatalog, xml_escape
strings = StringCatalog(__file__)
@@ -47,9 +47,11 @@
col = SystemTagsColumn(app, "Tags",
app.model.com_redhat_cumin_grid.Node.Tags, "Tags")
self.add_column(col)
+ col.cumin_module = "configuration"
col = SystemCheckinColumn(app, "Checkin",
app.model.com_redhat_cumin_grid.Node.Checkin, "Checkin")
self.add_column(col)
+ col.cumin_module = "configuration"
self.enable_csv_export()
@@ -74,6 +76,7 @@
configuration = SystemTags(app, "configuration", self.object)
self.view.add_tab(configuration)
+ configuration.cumin_module = "configuration"
def do_process(self, session):
try:
@@ -107,7 +110,7 @@
stat, value = item
if stat.name in self.fmt_as_bytes:
return fmt_bytes(value*1024)
- return CuminStatistic.fmt_value(value)
+ return CuminStatistic.fmt_value(value, escape=True)
class SystemStats(Widget):
def __init__(self, app, name, system):
@@ -194,7 +197,7 @@
tags = []
tags_string = ", ".join(tags)
- return tags_string
+ return xml_escape(tags_string)
def do_render(self, session, *args):
if self.defer_enabled and not getattr(session, "background", None):
@@ -448,6 +451,7 @@
self.attr = attr
self.title = title
self.format_method = getattr(attr, "unit", None)
+ self.cumin_module = "configuration"
def get_field(self):
field = CheckinField(self.table.adapter, self.name)
Modified: branches/stability/cumin/python/cumin/main.py
===================================================================
--- branches/stability/cumin/python/cumin/main.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/main.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -21,6 +21,8 @@
from user import *
from util import *
from widgets import *
+from authenticator import *
+from persona import CuminAuthorizator
from sage.catalog import Catalog
from sage.qmf.qmfoperations import QmfOperations
from sage.wallaby.wallabyoperations import WallabyOperations
@@ -33,18 +35,27 @@
class Cumin(Application):
def __init__(self, home, broker_uris, database_dsn,
- host="localhost", port=45672, persona="default"):
+ host="localhost", port=45672,
+ server_cert="", server_key="",
+ persona="default",
+ authmech=[]):
super(Cumin, self).__init__()
self.home = home
+ self.authmech = authmech
+ # Settings for LDAP authentication
+ self.ldap_timeout = None
+ self.ldap_tls_cacertdir = ""
+ self.ldap_tls_cacertfile = ""
+
model_dir = os.path.join(self.home, "model")
self.model = CuminModel(self, model_dir)
self.session = CuminSession(self, broker_uris)
self.database = CuminDatabase(self, database_dsn)
- self.server = CuminServer(self, host, port)
+ self.server = CuminServer(self, host, port, server_cert, server_key)
self.admin = CuminAdmin(self)
self.add_resource_dir(os.path.join(self.home, "resources-wooly"))
@@ -53,6 +64,10 @@
self.modules = list()
self.modules_by_name = dict()
+ # This is an argument to CuminAuthorizator in init()
+ self.access_path = None
+ self.do_authorize = False
+
self.tasks = list()
self.user = None
@@ -64,14 +79,10 @@
self.form_defaults = self.CuminFormDefaults()
- # Persona methods are used to control what pages and/or
- # modules are present
-
+ # Persona value maps to xml definitions handled
+ # in persona.py
self.persona = persona
- self.personas = dict()
- self.personas["default"] = self._init_default_persona
- self.personas["grid"] = self._init_grid_persona
- self.personas["messaging"] = self._init_messaging_persona
+
self._page_links = list()
# self.model.sql_logging_enabled = True
@@ -107,61 +118,66 @@
raise Exception(msg % self.home)
self.model.check()
+ self.database.check()
+
+ def _init_persona(self, modules):
- def _init_grid_persona(self):
- self.main_page = GridMainPage(self, "index.html")
+ # Determine the view based on which modules are enabled
+ if "messaging" in modules:
+ if "grid" in modules:
+ view = MainView(self, "main")
+ else:
+ view = MessagingMainView(self, "main")
+ else:
+ view = GridMainView(self, "main")
+
+ self.main_page = MainPage(self, "index.html", view)
self.add_page(self.main_page, add_to_link_set=True)
self.set_default_page(self.main_page)
-
- self.add_common_pages()
- import account
- import grid
- import inventory
- import usergrid
+ self.form_page = CuminFormPage(self, "form.html")
+ self.add_page(self.form_page)
- account.Module(self, "account")
- grid.Module(self, "grid")
- inventory.Module(self, "inventory")
- usergrid.Module(self, "usergrid")
+ self.add_page(StatFlashPage(self, "chart.json"))
+ self.add_page(FlashFullPage(self, "flashpage.html"))
- def _init_messaging_persona(self):
- self.main_page = MessagingMainPage(self, "index.html")
- self.add_page(self.main_page, add_to_link_set=True)
- self.set_default_page(self.main_page)
+ self.export_page = CuminExportPage(self, "csv")
+ self.add_page(self.export_page)
+
+ self.about_page = AboutPage(self, "about.html")
+ self.add_page(self.about_page)
- self.add_common_pages()
+ self.resource_page.protected = False
- import account
- import messaging
- import inventory
+ # Enable the modules that are associated with the
+ # persona (from persona.py)
+ for name in modules:
+ try:
+ m = __import__(name, globals())
+ m.Module(self, name)
+ except:
+ pass
- account.Module(self, "account")
- messaging.Module(self, "messaging")
- inventory.Module(self, "inventory")
+ def db_init(self, schema_version_check=True):
+ self.model.init()
+ self.database.init(schema_version_check)
- def _init_default_persona(self):
- self.main_page = MainPage(self, "index.html")
- self.add_page(self.main_page, add_to_link_set=True)
- self.set_default_page(self.main_page)
-
- self.add_common_pages()
-
- import account
- import messaging
- import grid
- import inventory
- import usergrid
-
- account.Module(self, "account")
- messaging.Module(self, "messaging")
- grid.Module(self, "grid")
- inventory.Module(self, "inventory")
- usergrid.Module(self, "usergrid")
-
- def init(self):
+ def init(self, schema_version_check=True):
log.info("Initializing %s", self)
+ # Do this initialization as late as possible so that
+ # the application can set config values.
+ self.authenticator = CuminAuthenticator(self)
+ self.authorizator = CuminAuthorizator(self, self.access_path, self.do_authorize)
+ try:
+ self.authorizator.set_persona(self.persona)
+ except:
+ msg = "Persona is not defined '%s'"
+ raise Exception(msg % self.persona)
+
+ self.authorize_cb = self.authorizator.authorize
+ self.mainpage_cb = self.authorizator.find_mainpage
+
# Create RPC interfaces for QMF and aviary.
# These service have overlapping functionality,
# so they are wrapped in a sage.Catalog object
@@ -212,14 +228,12 @@
self.model.init()
self.session.init()
- self.database.init()
+ self.database.init(schema_version_check)
self.server.init()
- # Create the correct persona
- if self.persona in self.personas:
- self.personas[self.persona]()
- else:
- self._init_default_persona()
+ # This will set up the main view and enable the
+ # modules that are associated with the persona
+ self._init_persona(self.authorizator.get_enabled_modules())
for module in self.modules:
module.init()
@@ -229,24 +243,6 @@
super(Cumin, self).init()
- def add_common_pages(self):
- self.form_page = CuminFormPage(self, "form.html")
- self.add_page(self.form_page)
-
- self.add_page(StatChartPage(self, "stats.png"))
- self.add_page(StatStackedPage(self, "stacked.png"))
- self.add_page(PieChartPage(self, PieChartPage.PAGE_NAME))
- self.add_page(StatFlashPage(self, "chart.json"))
- self.add_page(FlashFullPage(self, "flashpage.html"))
-
- self.export_page = CuminExportPage(self, "csv")
- self.add_page(self.export_page)
-
- self.about_page = AboutPage(self, "about.html")
- self.add_page(self.about_page)
-
- self.resource_page.protected = False
-
def start(self):
log.info("Starting %s", self)
@@ -288,9 +284,8 @@
def __init__(self, app, name):
self.app = app
self.name = name
-
self.tasks = list()
-
+ log.debug("Init cumin module %s", name )
assert not hasattr(app, self.name), self.name
self.app.modules.append(self)
@@ -303,45 +298,10 @@
def init_test(self, test):
pass
-class GridMainPage(CuminPage, ModeSet):
- def __init__(self, app, name):
- super(GridMainPage, self).__init__(app, name)
-
- self.main = GridMainView(app, "main")
- self.add_mode(self.main)
- self.set_default_frame(self.main)
-
- self.page_html_class = "Cumin"
-
- def render_title(self, session):
- return self.get_title(session)
-
- def get_title(self, session):
- return "Administrator"
-
class GridMainView(CuminMainView):
def __init__(self, app, name):
super(GridMainView, self).__init__(app, name)
- #self.overview = GridOverviewFrame(app, "overview")
- #self.add_tab(self.overview)
-
-class MessagingMainPage(CuminPage, ModeSet):
- def __init__(self, app, name):
- super(MessagingMainPage, self).__init__(app, name)
-
- self.main = MessagingMainView(app, "main")
- self.add_mode(self.main)
- self.set_default_frame(self.main)
-
- self.page_html_class = "Cumin"
-
- def render_title(self, session):
- return self.get_title(session)
-
- def get_title(self, session):
- return "Administrator"
-
class MessagingMainView(CuminMainView):
def __init__(self, app, name):
super(MessagingMainView, self).__init__(app, name)
@@ -349,13 +309,21 @@
self.overview = MessagingOverviewFrame(app, "overview")
self.add_tab(self.overview)
-class MainPage(CuminPage, ModeSet):
+class MainView(CuminMainView):
def __init__(self, app, name):
+ super(MainView, self).__init__(app, name)
+
+ self.overview = OverviewFrame(app, "overview")
+ self.add_tab(self.overview)
+
+class MainPage(CuminPage, ModeSet):
+ def __init__(self, app, name, main_view):
super(MainPage, self).__init__(app, name)
- self.main = MainView(app, "main")
+ self.main = main_view
self.add_mode(self.main)
self.set_default_frame(self.main)
+ self.cumin_module = ["messaging", "grid"]
self.page_html_class = "Cumin"
@@ -365,13 +333,6 @@
def get_title(self, session):
return "Administrator"
-class MainView(CuminMainView):
- def __init__(self, app, name):
- super(MainView, self).__init__(app, name)
-
- self.overview = OverviewFrame(app, "overview")
- self.add_tab(self.overview)
-
# XXX Add qmf tab
class OverviewFrame(CuminFrame):
@@ -409,46 +370,12 @@
systems = TopSystemTable(app, "systems")
self.add_child(systems)
+ systems.cumin_module = "inventory"
submissions = TopSubmissionTable(app, "submissions")
self.add_child(submissions)
+ submissions.cumin_module = "grid"
- # XXX
-
- #queues = messaging.queue.TopQueueSet(app, "queues")
- #self.add_child(queues)
-
- #submissions = grid.submission.TopSubmissionSet(app, "submissions")
- #self.add_child(submissions)
-
- #systems = inventory.system.TopSystemSet(app, "systems")
- #self.add_child(systems)
-
-# don't appear to need this....
- # class Heading(CuminHeading):
- # def render_title(self, session):
- # return "Overview"
-
- # def render_icon_href(self, session):
- # return "resource?name=action-36.png"
-
-class GridOverviewFrame(OverviewFrame):
- def __init__(self, app, name):
- super(GridOverviewFrame, self).__init__(app, name)
-
- def get_overview_view(self, app):
- return GridOverviewView(app, "view")
-
-class GridOverviewView(Widget):
- def __init__(self, app, name):
- super(GridOverviewView, self).__init__(app, name)
-
- systems = TopSystemTable(app, "systems")
- self.add_child(systems)
-
- submissions = TopSubmissionTable(app, "submissions")
- self.add_child(submissions)
-
class MessagingOverviewFrame(OverviewFrame):
def __init__(self, app, name):
super(MessagingOverviewFrame, self).__init__(app, name)
@@ -681,7 +608,6 @@
qdate = self.field.get_content(session, record)
if qdate is None:
return 0
-
return fmt_duration(time.time() - secs(qdate))
def render_text_align(self, session):
Modified: branches/stability/cumin/python/cumin/messaging/binding.py
===================================================================
--- branches/stability/cumin/python/cumin/messaging/binding.py 2012-05-31 12:18:57 UTC
(rev 5394)
+++ branches/stability/cumin/python/cumin/messaging/binding.py 2012-05-31 14:49:15 UTC
(rev 5395)
@@ -16,6 +16,7 @@
from wooly.parameters import DictParameter, StringParameter, ListParameter,\
IntegerParameter
from wooly.util import StringCatalog, Writer
+from cumin.util import xml_escape
strings = StringCatalog(__file__)
log = logging.getLogger("cumin.messaging.exchange")
@@ -80,7 +81,7 @@
def render_content(self, session):
# binding doesn't have a name, use the binding key
obj = self.object.get(session)
- return obj.bindingKey
+ return xml_escape(obj.bindingKey)
class BindingRemove(ObjectFrameTask):
def __init__(self, app, frame):
@@ -468,11 +469,11 @@
def render_n_value(self, session, i):
names = self.names.get(session)
- return len(names) > i and names[i] or ""
+ return len(names) > i and xml_escape(names[i]) or ""
def render_v_value(self, session, i):
values = self.values.get(session)
- return len(values) > i and values[i] or ""
+ return len(values) > i and xml_escape(values[i]) or ""
def get_exchange(self, session):
exchange_string = self.exchange.get(session)
Modified: branches/stability/cumin/python/cumin/messaging/brokerlink.py
===================================================================
--- branches/stability/cumin/python/cumin/messaging/brokerlink.py 2012-05-31 12:18:57 UTC
(rev 5394)
+++ branches/stability/cumin/python/cumin/messaging/brokerlink.py 2012-05-31 14:49:15 UTC
(rev 5395)
@@ -37,7 +37,7 @@
class BrokerLinkRemoveForm(ObjectFrameTaskForm):
def render_content(self, session):
obj = self.object.get(session)
- return obj.host
+ return xml_escape(obj.host)
class BrokerLinkRemove(ObjectFrameTask):
def __init__(self, app, frame):
@@ -185,13 +185,14 @@
if not self.param.get(session):
self.param.set(session, exchange.id)
exchanges.append(exchange)
+
return exchanges
def render_item_value(self, session, exchange):
return exchange.id
def render_item_content(self, session, exchange):
- return exchange.name
+ return xml_escape(exchange.name)
def render_item_checked_attr(self, session, exchange):
if self.param.get(session) == exchange.id:
Modified: branches/stability/cumin/python/cumin/messaging/connection.py
===================================================================
--- branches/stability/cumin/python/cumin/messaging/connection.py 2012-05-31 12:18:57 UTC
(rev 5394)
+++ branches/stability/cumin/python/cumin/messaging/connection.py 2012-05-31 14:49:15 UTC
(rev 5395)
@@ -101,8 +101,8 @@
def get_item_content(self, session, item):
args = (item.remoteProcessName, item.remotePid)
if args[1] is None:
- return item.address
- return "%s (%i)" % args
+ return xml_escape(item.address)
+ return xml_escape("%s (%i)" % args)
class ConnectionProcessColumn(ObjectTableColumn):
def __init__(self, app, name, attr, pid_attr):
Modified: branches/stability/cumin/python/cumin/messaging/exchange.py
===================================================================
--- branches/stability/cumin/python/cumin/messaging/exchange.py 2012-05-31 12:18:57 UTC
(rev 5394)
+++ branches/stability/cumin/python/cumin/messaging/exchange.py 2012-05-31 14:49:15 UTC
(rev 5395)
@@ -94,7 +94,7 @@
return "Remove"
def get_item_content(self, session, item):
- return item.name or "Default exchange"
+ return xml_escape(item.name) or "Default exchange"
def do_invoke(self, invoc, exchange):
session = self.app.model.get_session_by_object(exchange)
@@ -130,7 +130,7 @@
return exchange.id
def render_item_content(self, session, exchange):
- return exchange.name or "<em>Default</em>"
+ return xml_escape(exchange.name) or "<em>Default</em>"
def render_item_checked_attr(self, session, exchange):
return exchange is self.param.get(session) and
"checked=\"checked\""
Modified: branches/stability/cumin/python/cumin/messaging/main.py
===================================================================
--- branches/stability/cumin/python/cumin/messaging/main.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/messaging/main.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -16,6 +16,7 @@
super(Module, self).__init__(app, name)
self.frame = MessagingFrame(self.app, "messaging")
+ self.frame.cumin_module = name
def init(self):
super(Module, self).init()
Modified: branches/stability/cumin/python/cumin/messaging/queue.py
===================================================================
--- branches/stability/cumin/python/cumin/messaging/queue.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/messaging/queue.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -593,7 +593,7 @@
return queue_list
def render_item_content(self, session, queue):
- return queue.name or "<em>Default</em>"
+ return xml_escape(queue.name) or "<em>Default</em>"
def render_item_value(self, session, queue):
return queue._id
@@ -694,7 +694,7 @@
return "Source Queue"
def render_inputs(self, session):
- return self.get(session)
+ return xml_escape(self.get(session))
def get(self, session):
queue = self.form.object.get(session)
Modified: branches/stability/cumin/python/cumin/model.py
===================================================================
--- branches/stability/cumin/python/cumin/model.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/model.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -8,7 +8,7 @@
from cumin.formats import fmt_datetime, fmt_dict, fmt_none_brief, fmt_none,\
fmt_duration
-from cumin.util import calc_rate, JobStatusInfo, secs
+from cumin.util import calc_rate, JobStatusInfo, secs, xml_escape
from cumin.sqladapter import SqlAdapter
from rosemary.sqlfilter import SqlComparisonFilter
from rosemary.sqlquery import SqlQueryOptions
@@ -439,14 +439,20 @@
def get_title(self, session):
return self.name
- def format_value(self, session, value):
- return CuminStatistic.fmt_value(value)
+ def format_value(self, session, value, escape=False):
+ return CuminStatistic.fmt_value(value, escape)
@classmethod
- def fmt_value(cls, value):
+ def fmt_value(cls, value, escape=False):
try:
+ # Don't want to escape the None case because
+ # the formatter produces xml, and we don't
+ # need to escape the number cases either.
return cls.formatters[type(value)](value)
except KeyError:
+ # But we do optionally want to escape the string cases
+ if escape:
+ return xml_escape(value)
return value
class MetaData(object):
Modified: branches/stability/cumin/python/cumin/objectframe.py
===================================================================
--- branches/stability/cumin/python/cumin/objectframe.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/objectframe.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -93,8 +93,10 @@
obj = self.object.get(session)
for attr in self.get_attributes(session):
+ # name and title are from rosemary xml files
name = attr.title
- value = obj.get_formatted_value(attr.name)
+ # We are going to escape the value in ObjectAttributesEntry below
+ value = obj.get_formatted_value(attr.name, escape=False)
title = attr.description
writer.write(self.entry.render(session, name, value, title))
@@ -284,7 +286,7 @@
return cls
def render_content(self, session, frame):
- return frame.get_title(session)
+ return xml_escape(frame.get_title(session))
class ObjectViewHeading(ObjectViewChild):
def __init__(self, app, name, object):
@@ -297,7 +299,7 @@
retval = self.parent.render_title(session)
if retval is not None and isinstance(retval, str) and len(retval) > 100:
retval = retval[:100] + "..."
- return retval
+ return xml_escape(retval)
class ObjectViewSummary(ObjectViewChild):
def __init__(self, app, name, object):
@@ -467,7 +469,7 @@
return super(ObjectFrameTaskForm, self).render_content(session)
obj = self.object.get(session)
- return obj.get_title()
+ return xml_escape(obj.get_title())
class ObjectFrameTaskFeedbackForm(ObjectFrameTaskForm):
# substitute our submit button because it has feedback when it is clicked
Modified: branches/stability/cumin/python/cumin/objectselector.py
===================================================================
--- branches/stability/cumin/python/cumin/objectselector.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/objectselector.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -1,7 +1,7 @@
from cumin.objectframe import ObjectFrame
from cumin.qmfadapter import ObjectQmfAdapter, ObjectQmfField
from cumin.sqladapter import ObjectSqlAdapter, ObjectSqlField
-from cumin.util import Identifiable
+from cumin.util import Identifiable, xml_escape
from rosemary.model import RosemaryClass, RosemaryAttribute, RosemaryReference
from rosemary.sqlfilter import SqlLikeFilter
@@ -489,7 +489,7 @@
pass
def get_item_content(self, session, item):
- return item.get_formatted_value("name")
+ return item.get_formatted_value("name", escape=True)
class ObjectSelectorTaskForm(FoldingFieldSubmitForm):
def __init__(self, app, name, task):
@@ -545,6 +545,7 @@
return self.parent.selection.get(session)
def render_item_content(self, session, item):
+ # already escaped in get_item_content
return self.parent.task.get_item_content(session, item)
def render_item_class(self, session, item):
Copied: branches/stability/cumin/python/cumin/persona.py (from rev 5295,
trunk/cumin/python/cumin/persona.py)
===================================================================
--- branches/stability/cumin/python/cumin/persona.py (rev 0)
+++ branches/stability/cumin/python/cumin/persona.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+import logging
+import os.path
+import re
+
+try:
+ import xml.etree.ElementTree as etree
+except ImportError:
+ import elementtree.ElementTree as etree
+
+log = logging.getLogger("cumin.authorize")
+
+class CuminGroup(object):
+ def __init__(self, name):
+ self.name = name
+ self.mainpage = "index.html"
+ self.allowed = [] # which modules the group can access
+
+class CuminPersona(object):
+ def __init__(self, name, auth):
+ self.name = name # may be handy to have this here
+
+ self.auth = auth.lower() == "true" # should groups be checked?
+ self.modules = [] # which modules are enabled
+ self.mapping = {} # dictionary of CuminGroup objects
+
+ def find_mainpage(self, group):
+ return self.mapping[group].mainpage
+
+ def lookup(self, group, module):
+ try:
+ if type(module) in (list, tuple):
+ return len(set(module) & \
+ set(self.mapping[group].allowed)) > 0
+ else:
+ return module in self.mapping[group].allowed
+ except KeyError:
+ return False
+
+class CuminPersonaMap(object):
+ def __init__(self):
+ log.info("Initializing %s", self.__class__.__name__)
+ self.personas = {}
+ self.filepath = None
+
+ def __repr__(self):
+ return ("%s %s" % ( self.__class__.__name__, self.filepath ))
+
+ def parse_personafile(self, filepath):
+ #TODO: handle exceptions during parsing
+ acctree = etree.ElementTree().parse(filepath)
+ for prec in acctree.getiterator('Persona'):
+ pkey = prec.attrib["name"]
+ auth = prec.attrib["auth"]
+ persona = CuminPersona(pkey, auth)
+ self.personas[pkey] = persona
+
+ for pchild in prec.getchildren():
+ if pchild.tag == "Module":
+ persona.modules.append(pchild.attrib["name"])
+ elif pchild.tag == "GroupAccess":
+ gkey = pchild.attrib["name"]
+ group = CuminGroup(gkey)
+ persona.mapping[gkey] = group
+ for gchild in pchild.getchildren():
+ if gchild.tag == "MainPage":
+ group.mainpage = gchild.attrib["name"]
+ elif gchild.tag == "ModuleAccess":
+ if gchild.attrib["name"] == "*":
+ group.allowed.extend(persona.modules)
+ else:
+ group.allowed.append(gchild.attrib["name"])
+
+class CuminAuthorizator(object):
+ def __init__(self, app, access_path, do_authorize):
+ self.app = app
+ self.access_path = access_path
+ log.info("Initializing %s", self)
+ self.personas = CuminPersonaMap()
+ self.persona = None
+ self.do_authorize = do_authorize
+
+ log.debug("Access file path is %s", access_path)
+ if os.path.isdir(access_path):
+ files = [os.path.join(access_path, x) \
+ for x in os.listdir(access_path)]
+ elif os.path.isfile(access_path):
+ files = [access_path]
+ else:
+ files = []
+ log.error("Path '%s' for access files does not exist!" %
access_path)
+
+ for accfile in files:
+ if os.path.isfile(accfile) and \
+ os.path.splitext(accfile)[1] == ".xml":
+ try:
+ self.personas.parse_personafile(accfile)
+ except:
+ log.error("Access file '%s' does not parse!" %
accfile)
+
+ def set_persona(self, name):
+ self.persona = self.personas.personas[name]
+
+ def is_enforcing(self):
+ return self.do_authorize and self.persona.auth
+
+ def find_mainpage(self, web_session):
+ # Find a main page for the current login session.
+ # If user is in the admin group, prefer that one.
+ # If we have multiple groups someday beyond user/admin,
+ # we may have to have a priority scheme here (or find
+ # a way to NOT have more than one mainpage).
+ try:
+ group = web_session.client_session.attributes['login_session'].group
+ except KeyError:
+ group = "nogroup"
+ if type(group) == type([]):
+ if "admin" in group:
+ group = "admin"
+ else:
+ group = group[0]
+ return self.persona.find_mainpage(group)
+
+ def get_enabled_modules(self):
+ return self.persona.modules
+
+ def contains_valid_group(self, groups):
+ for g in groups:
+ if g in self.persona.mapping:
+ return True
+ return False
+
+ def authorize(self, web_session, widget):
+ if not hasattr(widget, "cumin_module") or \
+ widget.cumin_module is None:
+ # no module for this widget, we have to allow it
+ return True
+
+ # We can't logically be authorized for a module we haven't even
+ # enabled! This may occur when parts of a display have
+ # associated themselves with a module explicitly.
+ # (like a column in a table that is only valid if a
+ # particular module has been loaded).
+ if type(widget.cumin_module) in (list, tuple):
+ if len(set(widget.cumin_module) & set(self.persona.modules)) == 0:
+ return False
+ elif not widget.cumin_module in self.persona.modules:
+ return False
+
+ if not self.is_enforcing():
+ # Auth checks against group are off.
+ return True
+
+ try:
+ group = web_session.client_session.attributes['login_session'].group
+ except KeyError:
+ group = "nogroup"
+
+ if type(group) == type([]):
+ for g in group:
+ if self.persona.lookup(g, widget.cumin_module):
+ log.debug("Allowing %s authorization for %s ",
+ widget.cumin_module, group)
+ return True
+ else:
+ return self.persona.lookup(group, widget.cumin_module)
+
+ log.debug("Denying %s authorization for %s ", widget.my_name, group)
+ return False
+
Modified: branches/stability/cumin/python/cumin/server.py
===================================================================
--- branches/stability/cumin/python/cumin/server.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/server.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -3,5 +3,12 @@
from wooly.server import WebServer
class CuminServer(WebServer):
+ def __init__(self, app, host, port, server_cert="",
server_key=""):
+
+ # Elements of WebServer will look for the cert/key fields
+ self.server_cert = server_cert
+ self.server_key = server_key
+ super(CuminServer, self).__init__(app, host, port)
+
def authorized(self, session):
return True
Modified: branches/stability/cumin/python/cumin/stat.py
===================================================================
--- branches/stability/cumin/python/cumin/stat.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/stat.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -15,7 +15,7 @@
from cumin.widgets import StateSwitch
from wooly.template import WidgetTemplate
from cumin.charts import CuminPieChart, TimeSeriesChart, StackedValueChart
-from cumin.util import calc_rate, secs, nvl
+from cumin.util import calc_rate, secs, nvl, xml_escape
from cumin.OpenFlashChart import Element, Chart
strings = StringCatalog(__file__)
@@ -55,7 +55,7 @@
def render_item_value(self, session, item):
value = self.get_item_value(session, item)
- return self.render_formatted_value(session, item, value)
+ return self.render_formatted_value(session, item, value, escape=True)
def get_item_unit(self, session, item):
stat, _ = item
@@ -65,7 +65,10 @@
_, value = item
return value
- def render_formatted_value(self, session, item, value):
+ def render_formatted_value(self, session, item, value, escape=False):
+ # Since this routine delegates to CuminStatistic which may produce
+ # xml for None values, we need to handle escaping here and in
+ # CuminStatistic
def fmt_b(value):
return value is not None and fmt_bytes(value) or None
@@ -85,7 +88,7 @@
except:
pass
- return CuminStatistic.fmt_value(value)
+ return CuminStatistic.fmt_value(value, escape)
class NewStatSet(ItemSet):
def __init__(self, app, name):
@@ -113,7 +116,7 @@
def render_item_value(self, session, item):
stat, value = item
- return stat.format_value(session, value)
+ return stat.format_value(session, value, escape=True)
class DurationSwitch(StateSwitch):
def __init__(self, app, name):
Modified: branches/stability/cumin/python/cumin/usergrid/main.py
===================================================================
--- branches/stability/cumin/python/cumin/usergrid/main.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/cumin/python/cumin/usergrid/main.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -11,3 +11,4 @@
self.app.user_grid_page = MainPage(self.app, "usergrid.html")
self.app.add_page(self.app.user_grid_page, add_to_link_set=True)
+ self.app.user_grid_page.cumin_module = self.name
Modified: branches/stability/cumin/python/cumin/util.py
===================================================================
--- branches/stability/cumin/python/cumin/util.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/util.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -11,14 +11,9 @@
from random import sample
from threading import Thread, Event
from time import mktime, time, sleep
-from xml.sax.saxutils import escape as do_xml_escape
-
from parsley.threadingex import print_threads
+from wooly.util import xml_escape
-def xml_escape(string):
- if string:
- return do_xml_escape(string)
-
def short_id():
return "%08x" % randint(0, sys.maxint)
Modified: branches/stability/cumin/python/cumin/widgets.py
===================================================================
--- branches/stability/cumin/python/cumin/widgets.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/cumin/python/cumin/widgets.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -63,7 +63,7 @@
def render_user_name(self, session):
login = session.client_session.attributes["login_session"]
- return login.user.name
+ return xml_escape(login.user.name)
def render_logout_href(self, session):
page = self.app.login_page
@@ -83,7 +83,7 @@
return branch.marshal()
else:
return super(CuminMainView, self).render_tab_href(session, tab)
-
+
def render_about_href(self, session):
page = self.app.about_page
lsess = Session(page)
@@ -92,29 +92,24 @@
class CuminPageLinks(ItemSet):
def __init__(self, app, name):
super(CuminPageLinks, self).__init__(app, name)
-
+ self.app = app
self.html_class = CuminPageLinks.__name__
- self.check_admin = self.CheckAdmin(app)
-
def do_get_items(self, session):
return self.app.page_links
def render_item_content(self, session, page):
href = Session(page).marshal()
title = page.render_title(session)
+ if self.app.authorize_cb(session, page):
+ return fmt_link(href, title)
- return fmt_link(href, title)
-
def render_item_class(self, session, page):
if page is session.page:
return "selected"
else:
return "_"
- class CheckAdmin(CuminSqlDataSet):
- pass
-
class CuminFrame(Frame, ModeSet):
def __init__(self, app, name):
super(CuminFrame, self).__init__(app, name)
@@ -158,7 +153,6 @@
def render_href(self, session, *args):
branch = session.branch()
-
if hasattr(self, "view"):
self.view.show(branch)
@@ -1194,14 +1188,24 @@
# XXX this should move somewhere else
class LoginSession(object):
- def __init__(self, app, user):
+ def __init__(self, app, user, group):
self.app = app
- self.user = user
+ self.group = group
+ # If this is an external user, create
+ # an adapter here. Lots of things
+ # expect user.name to be defined.
+ if type(user) is str:
+ self.user = LoginSession.ExternalUser()
+ self.user.name = user
+ else:
+ self.user = user
self.created = datetime.now()
-
self.task_invocations = list()
+ class ExternalUser(object):
+ pass
+
class CuminPage(HtmlPage):
def __init__(self, app, name):
super(CuminPage, self).__init__(app, name)
@@ -1214,44 +1218,66 @@
self.error_tmpl = WidgetTemplate(self, "error_html")
self.not_found_tmpl = WidgetTemplate(self, "not_found_html")
- def do_process(self, session):
+ def _redirect_to_login(self, session):
session.cursor = self.app.database.get_read_cursor()
-
if not self.authorized(session):
page = self.app.login_page
-
sess = Session(page)
page.origin.set(sess, session.marshal())
-
self.redirect.set(session, sess.marshal())
+ return True
+ return False
- return
+ def redirect_on_not_authorized(self, session):
+ # If the user is not logged in, then it doesn't have a
+ # a group identity yet. Check to see if we should
+ # redirect to the login page before looking for a
+ # valid page to redirect to based on group
+ if not self._redirect_to_login(session):
+ super(CuminPage, self).redirect_on_not_authorized(session)
- super(CuminPage, self).do_process(session)
+ def do_process(self, session):
+ if not self._redirect_to_login(session):
+ super(CuminPage, self).do_process(session)
def authorized(self, session):
if not self.protected:
return True
login = session.client_session.attributes.get("login_session")
-
if login:
when = datetime.now() - timedelta(hours=24)
-
if login.created > when:
return True
+
elif self.app.user:
- cls = self.app.model.com_redhat_cumin.User
- users = cls.get_selection(session.cursor, name=self.app.user)
+ username = self.app.user
+ user, ok = self.app.authenticator.find_user(username)
+ if not ok:
+ # We couldn't find the user, internally or externally
+ log.info("User '%s' not found" % username)
+ return False
- if not users:
- raise Exception("User '%s' not found" % self.app.user)
+ # Check for valid group
+ if self.app.authorizator.is_enforcing():
+ if not user:
+ # This is an external user, default role is 'user'
+ roles = ['user']
+ else:
+ cursor = self.app.database.get_read_cursor()
+ roles = self.app.admin.get_roles_for_user(
+ cursor, user)
+ if not self.app.authorizator.contains_valid_group(roles):
+ log.info("No valid roles for '%s'" % username)
+ return False # go to login page
+ else:
+ roles = []
- login = LoginSession(self.app, users[0])
+ if user is None:
+ user = username
+ login = LoginSession(self.app, user, roles)
session.client_session.attributes["login_session"] = login
-
return True
-
return False
def render_error(self, session):
@@ -1395,15 +1421,16 @@
super(BaseBindingInput, self).__init__(app, name, field_param)
def render_item_content(self, session, field):
- return field.name or "Default"
+ return xml_escape(field.name) or "Default"
def render_value(self, session):
if self.disabled:
field = self.form.object.get(session)
- return fmt_shorten(field.get_formatted_value("name"), pre=36,
post=4)
+ return fmt_shorten(field.get_formatted_value("name", escape=True),
+ pre=36, post=4)
else:
input_value = self.param.get(session)
- return input_value and input_value or ""
+ return input_value and xml_escape(input_value) or ""
def base_get_items(self, session, objects):
obj_list = []
@@ -1621,4 +1648,4 @@
def render_listcontainer_height(self, session):
return "%dpx" % self.listcontainer_height
-
\ No newline at end of file
+
Modified: branches/stability/mint/python/mint/database.py
===================================================================
--- branches/stability/mint/python/mint/database.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/mint/python/mint/database.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -14,22 +14,31 @@
def check(self):
log.info("Checking %s", self)
+ # checks moved to init because the model has to be
+ # initialized before we can check the schema. And
+ # there is no point connecting to the database twice...
- self.check_connection()
-
def init(self):
log.info("Initializing %s", self)
-
- def check_connection(self):
- conn = self.get_connection()
-
+ conn = None
try:
+ conn = self.get_connection()
cursor = conn.cursor()
- cursor.execute("select now()")
-
- log.debug("Database is talking at '%s'", self.dsn)
- finally:
+ self.check_connection(cursor)
+ self.app.admin.check_schema(cursor)
conn.close()
+ except psycopg2.OperationalError:
+ conn and conn.close()
+ log.error("Connection to database failed, is the database
running?")
+ raise
+ except Exception, e:
+ conn and conn.close()
+ log.error(str(e))
+ raise
+ def check_connection(self, cursor):
+ cursor.execute("select now()")
+ log.debug("Database is talking at '%s'", self.dsn)
+
def __repr__(self):
return self.__class__.__name__
Modified: branches/stability/mint/python/mint/main.py
===================================================================
--- branches/stability/mint/python/mint/main.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/mint/python/mint/main.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -4,7 +4,7 @@
from session import MintSession
from update import UpdateThread
from vacuum import VacuumThread
-
+from cumin.admin import *
from util import *
log = logging.getLogger("mint.main")
@@ -16,7 +16,7 @@
self.session = MintSession(self, broker_uris)
self.database = MintDatabase(self, database_dsn)
-
+ self.admin = CuminAdmin(self)
self.update_thread = UpdateThread(self)
self.expire_enabled = True
Modified: branches/stability/rosemary/python/rosemary/model.py
===================================================================
--- branches/stability/rosemary/python/rosemary/model.py 2012-05-31 12:18:57 UTC (rev
5394)
+++ branches/stability/rosemary/python/rosemary/model.py 2012-05-31 14:49:15 UTC (rev
5395)
@@ -3,6 +3,7 @@
from sqlquery import *
from sqltype import *
import util
+from cumin.util import xml_escape
log = logging.getLogger("rosemary.model")
@@ -833,10 +834,12 @@
for attr in ("name", "Name"):
if hasattr(self, attr):
- return self.get_formatted_value(attr)
+ return self.get_formatted_value(attr, escape=True)
- def get_formatted_value(self, attr):
+ def get_formatted_value(self, attr, escape=False):
value = self.get_value(attr)
+ if escape:
+ value = xml_escape(value)
formatter = None
if attr in self._class._properties_by_name:
formatter = self._class._properties_by_name[attr].formatter
Modified: branches/stability/wooly/python/wooly/__init__.py
===================================================================
--- branches/stability/wooly/python/wooly/__init__.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/__init__.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -2,7 +2,7 @@
from resources import ResourceFinder
from template import *
from util import *
-
+from traceback import print_exception
log = logging.getLogger("wooly")
strings = StringCatalog(__file__)
@@ -125,6 +125,8 @@
self.__defer_tmpl = WidgetTemplate(self, "deferred_html")
self.nocontent_tmpl = WidgetTemplate(self, "nocontent")
+ self.my_name = self.__class__.__module__ + '.' + self.__class__.__name__
+
def init(self):
log.debug("Initializing %s" % self)
@@ -285,26 +287,36 @@
for child in self.children:
child.save_parameters(session, params)
+ def redirect_on_not_authorized(self, session):
+ # Give an opportunity to redirect to a valid page
+ # when a user is not authorized for this widget.
+ pass
+
def process(self, session, *args):
- if self.update_enabled:
- self.page.enable_update(session, self)
+ if not self.app.authorize_cb(session, self):
+ self.redirect_on_not_authorized(session)
+ else:
+ if self.update_enabled:
+ self.page.enable_update(session, self)
- if self.defer_enabled:
- self.page.enable_defer(session, self)
+ if self.defer_enabled:
+ self.page.enable_defer(session, self)
- if self.app.debug:
- profile = self.page.profile.get(session)
+ if self.app.debug:
+ profile = self.page.profile.get(session)
- call = ProcessCall(profile, self, args)
- call.do(session)
- else:
- self.do_process(session, *args)
+ call = ProcessCall(profile, self, args)
+ call.do(session)
+ else:
+ self.do_process(session, *args)
def do_process(self, session, *args):
for child in self.children:
child.process(session)
def render(self, session, *args):
+ if not self.app.authorize_cb(session, self):
+ return ""
if self.app.debug:
profile = self.page.profile.get(session)
@@ -479,7 +491,12 @@
return self.render(session)
def service_error(self, session):
- self.error.set(session, PageError(self, session))
+ # Well, we can have other handled exceptions pop up before
+ # we actually get to the render so we want to record here
+ # what the root cause was.
+ cls, value, traceback = sys.exc_info()
+ self.error.set(session, PageError(self, session,
+ cls, value, traceback))
return self.render(session)
@@ -503,15 +520,18 @@
pass
class PageError(object):
- def __init__(self, page, session):
+ def __init__(self, page, session, cls, value, traceback):
self.page = page
self.session = session
+ self.cls = cls
+ self.value = value
+ self.traceback = traceback
def render(self):
writer = Writer()
writer.write("APPLICATION ERROR\n\n")
- print_exc(None, writer)
+ print_exception(self.cls, self.value, self.traceback, None, writer)
writer.write("\n")
Modified: branches/stability/wooly/python/wooly/forms.py
===================================================================
--- branches/stability/wooly/python/wooly/forms.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/forms.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -53,7 +53,7 @@
class FormError(object):
def __init__(self, message):
- self.message = message
+ self.message = xml_escape(message)
def get_message(self, session):
return self.message
@@ -133,6 +133,26 @@
title = self.widget.render_title(session)
return "The '%s' field must have a value" % title
+class QuoteValueError(FormError):
+ def __init__(self, widget):
+ super(QuoteValueError, self).__init__(None)
+
+ self.widget = widget
+
+ def get_message(self, session):
+ title = self.widget.render_title(session)
+ return "The '%s' field may not contain double quotes" % title
+
+class XMLValueError(FormError):
+ def __init__(self, widget):
+ super(XMLValueError, self).__init__(None)
+
+ self.widget = widget
+
+ def get_message(self, session):
+ title = self.widget.render_title(session)
+ return "The '%s' field may not contain XML special characters"
% title
+
class EmptyInputError(FormError):
def __init__(self):
super(EmptyInputError, self).__init__("This value is required")
@@ -453,6 +473,19 @@
self.input = StringInput(app, "input")
self.add_child(self.input)
+class NoXMLStringField(StringField):
+ def validate(self, session):
+ super(NoXMLStringField, self).validate(session)
+
+ des = self.get(session)
+ if '"' in des:
+ error = QuoteValueError(self)
+ self.form.errors.add(session, error)
+
+ elif xml_escape(des) != des:
+ error = XMLValueError(self)
+ self.form.errors.add(session, error)
+
class MultilineStringField(ScalarField):
def __init__(self, app, name):
super(MultilineStringField, self).__init__(app, name, None)
Modified: branches/stability/wooly/python/wooly/pages.py
===================================================================
--- branches/stability/wooly/python/wooly/pages.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/pages.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -142,6 +142,14 @@
return sess.marshal()
+ def redirect_on_not_authorized(self, session):
+ mainpage = self.app.mainpage_cb(session)
+ if mainpage in self.app.pages_by_name:
+ log.debug("Not authorized, redirecting to %s", mainpage)
+ page = self.app.pages_by_name[mainpage]
+ sess = Session(page)
+ self.redirect.set(session, sess.marshal())
+
def do_process(self, session):
self.update_script.process(session)
self.defer_script.process(session)
Modified: branches/stability/wooly/python/wooly/server.py
===================================================================
--- branches/stability/wooly/python/wooly/server.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/server.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -212,7 +212,34 @@
request_queue_size=32,
numthreads=32,
max=32)
+
+ if self.server.server_cert and self.server.server_key:
+ ssl_adapter = None
+ # Try the Python ssl module solution first
+ try:
+ from wsgiserver.ssl_builtin import BuiltinSSLAdapter
+ ssl_adapter = BuiltinSSLAdapter(self.server.server_cert,
+ self.server.server_key)
+ log.info("Webserver: ssl enabled via the Python ssl module.")
+ except:
+ pass
+
+ if ssl_adapter is None:
+ try:
+ from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
+ ssl_adapter = pyOpenSSLAdapter(self.server.server_cert,
+ self.server.server_key)
+ log.info("Webserver: ssl enabled via the pyOpenSSL
module.")
+ except:
+ pass
+
+ if ssl_adapter is None:
+ log.warn("Webserver: SSL was requested, but imports failed "
+ "for SSL implementations. Running with http only.")
+ else:
+ self.wsgi_server.ssl_adapter = ssl_adapter
+
self.wsgi_server.environ["wsgi.version"] = (1, 1)
def run(self):
Modified: branches/stability/wooly/python/wooly/table.py
===================================================================
--- branches/stability/wooly/python/wooly/table.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/table.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -70,6 +70,7 @@
writer = Writer()
for column in self.get_visible_columns(session):
+
writer.write(column.render(session))
return writer.to_string()
@@ -114,6 +115,7 @@
self.visible = True
self.width = None
self.static_header = False
+ self.do_escape = True
def render_class(self, session):
tokens = list()
@@ -171,8 +173,23 @@
def render_header_link_class(self, session):
pass
+ def do_render_cell_content(self, session, record):
+ r = self.render_cell_content(session, record)
+ if self.do_escape:
+ return xml_escape(r)
+ return r
+
def render_cell_content(self, session, record):
pass
+
+ def do_render_cell_title(self, session, record):
+ r = self.render_cell_title(session, record)
+ if self.do_escape:
+ return xml_escape(r)
+ return r
+
+ def render_cell_title(self, session, record):
+ pass
class TableColumnCss(TableChild):
def __init__(self, app, name):
@@ -242,11 +259,11 @@
return self.parent.render_class(session)
def render_content(self, session, record):
- return self.parent.render_cell_content(session, record)
+ return self.parent.do_render_cell_content(session, record)
def render_cell_title(self, session, record):
#gives us the title="" attribute for each table cell
- return self.parent.render_cell_title(session, record)
+ return self.parent.do_render_cell_title(session, record)
class TableHeader(TableChild):
def render_colspan(self, session):
@@ -256,7 +273,8 @@
writer = Writer()
for column in self.table.get_visible_columns(session):
- writer.write(column.header.render(session))
+ if self.app.authorize_cb(session, column):
+ writer.write(column.header.render(session))
return writer.to_string()
@@ -269,7 +287,8 @@
writer = Writer()
for column in self.table.get_visible_columns(session):
- writer.write(column.cell.render(session, record))
+ if self.app.authorize_cb(session, column):
+ writer.write(column.cell.render(session, record))
return writer.to_string()
Modified: branches/stability/wooly/python/wooly/util.py
===================================================================
--- branches/stability/wooly/python/wooly/util.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/util.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -18,6 +18,11 @@
from parsley.collectionsex import *
+def xml_escape(string, entities=None):
+ if type(string) in (str, unicode):
+ return escape(string, entities)
+ return string
+
def unique_id():
bits0 = random.getrandbits(32)
bits1 = random.getrandbits(32)
Modified: branches/stability/wooly/python/wooly/widgets.py
===================================================================
--- branches/stability/wooly/python/wooly/widgets.py 2012-05-31 12:18:57 UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/widgets.py 2012-05-31 14:49:15 UTC (rev 5395)
@@ -58,7 +58,8 @@
writer = Writer()
for tab in self.__tabs:
- self.__tab_tmpl.render(writer, session, tab)
+ if self.app.authorize_cb(session, tab):
+ self.__tab_tmpl.render(writer, session, tab)
return writer.to_string()
@@ -197,7 +198,7 @@
return writer.to_string()
def render_item_content(self, session, item):
- return item
+ return xml_escape(item)
def render_item_class(self, session, item):
return "_"
Modified: branches/stability/wooly/python/wooly/wsgiserver/ssl_builtin.py
===================================================================
--- branches/stability/wooly/python/wooly/wsgiserver/ssl_builtin.py 2012-05-31 12:18:57
UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/wsgiserver/ssl_builtin.py 2012-05-31 14:49:15
UTC (rev 5395)
@@ -14,7 +14,7 @@
except ImportError:
ssl = None
-from cherrypy import wsgiserver
+from wooly import wsgiserver
class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
Modified: branches/stability/wooly/python/wooly/wsgiserver/ssl_pyopenssl.py
===================================================================
--- branches/stability/wooly/python/wooly/wsgiserver/ssl_pyopenssl.py 2012-05-31 12:18:57
UTC (rev 5394)
+++ branches/stability/wooly/python/wooly/wsgiserver/ssl_pyopenssl.py 2012-05-31 14:49:15
UTC (rev 5395)
@@ -34,7 +34,7 @@
import threading
import time
-from cherrypy import wsgiserver
+from wooly import wsgiserver
try:
from OpenSSL import SSL