diff --git a/Makefile.am b/Makefile.am index b2abf9b65..57f599f0b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,511 +1,513 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. confignicedir = $(sysconfdir)/build confignice_SCRIPTS=config.nice SUBDIRS = po config modules EXTRA_DIST = UNINSTALL THANKS RELEASE-NOTES configure-tests.py config.nice.in \ config.rpath # current jsMath version and packages JSMV = 3.6b JSMFV = 1.3 JSMATH = jsMath-$(JSMV).zip JSMATHFONTS = jsMath-fonts-$(JSMFV).zip # current FCKeditor version FCKV = 2.6.6 FCKEDITOR = FCKeditor_$(FCKV).zip # git-version-get stuff: BUILT_SOURCES = $(top_srcdir)/.version $(top_srcdir)/.version: echo $(VERSION) > $@-t && mv $@-t $@ dist-hook: echo $(VERSION) > $(distdir)/.tarball-version check-custom-templates: $(PYTHON) $(top_srcdir)/modules/webstyle/lib/template.py --check-custom-templates $(top_srcdir) kwalitee-check: @$(PYTHON) $(top_srcdir)/modules/miscutil/lib/kwalitee.py --stats $(top_srcdir) kwalitee-check-errors-only: @$(PYTHON) $(top_srcdir)/modules/miscutil/lib/kwalitee.py --check-errors $(top_srcdir) kwalitee-check-variables: @$(PYTHON) $(top_srcdir)/modules/miscutil/lib/kwalitee.py --check-variables $(top_srcdir) kwalitee-check-indentation: @$(PYTHON) $(top_srcdir)/modules/miscutil/lib/kwalitee.py --check-indentation $(top_srcdir) kwalitee-check-sql-queries: @echo "* Listing potentially dangerous SQL queries:" @echo "** SQL SELECT queries without explicit column list:" @find $(top_srcdir) -name '*.py' -exec grep -HEin 'SELECT \* FROM' {} \; 2> /dev/null @echo "** SQL INSERT queries without explicit column list:" @find $(top_srcdir) -name '*.py' -exec grep -HEin 'INSERT INTO ([[:alnum:]]|_)+[[:space:]]*VALUES' {} \; 2> /dev/null @find $(top_srcdir) -name '*.py' -exec grep -HEin 'INSERT INTO ([[:alnum:]]|_)+[[:space:]]*$$' {} \; 2> /dev/null @echo "** SQL queries using charset-ignorant escape_string():" @find $(top_srcdir) -name '*.py' -exec grep -HEin 'escape_string' {} \; 2> /dev/null @echo "** SQL queries using literal '%s':" @find $(top_srcdir) -name '*.py' -exec grep -HEin "run_sql.*'%[dfis]'" {} \; 2> /dev/null @find $(top_srcdir) -name '*.py' -exec grep -HEin 'run_sql.*"%[dfis]"' {} \; 2> /dev/null @echo "** SQL queries with potentially unescaped arguments:" @find $(top_srcdir) -name '*.py' -exec grep -HEin 'run_sql.* % ' {} \; 2> /dev/null @echo "* Done." etags: \rm -f $(top_srcdir)/TAGS (cd $(top_srcdir) && find $(top_srcdir) -name "*.py" -print | xargs etags) install-data-local: for d in / /cache /log /tmp /data /run ; do \ mkdir -p $(localstatedir)$$d ; \ done @echo "************************************************************" @echo "** Invenio software has been successfully installed! **" @echo "** **" @echo "** You may proceed to customizing your installation now. **" @echo "************************************************************" install-jsmath-plugin: @echo "***********************************************************" @echo "** Installing jsMath plugin, please wait... **" @echo "***********************************************************" rm -rf /tmp/invenio-jsmath-plugin mkdir /tmp/invenio-jsmath-plugin (cd /tmp/invenio-jsmath-plugin && \ wget 'http://downloads.sourceforge.net/jsmath/$(JSMATH)' && \ wget 'http://downloads.sourceforge.net/jsmath/$(JSMATHFONTS)' && \ wget 'http://www.math.union.edu/~dpvc/jsMath/download/extra-fonts/msam10/msam10.zip' && \ wget 'http://www.math.union.edu/~dpvc/jsMath/download/extra-fonts/msbm10/msbm10.zip' && \ unzip -u -d ${prefix}/var/www $(JSMATH) && \ unzip -u -d ${prefix}/var/www $(JSMATHFONTS) && \ unzip -u -d ${prefix}/var/www/jsMath/fonts msam10.zip && \ unzip -u -d ${prefix}/var/www/jsMath/fonts msbm10.zip) rm -fr /tmp/invenio-jsmath-plugin @echo "* Installing Invenio-specific jsMath config..." (cd $(top_srcdir)/modules/webstyle/etc && make install) @echo "***********************************************************" @echo "** The jsMath plugin was successfully installed. **" @echo "** Please do not forget to properly set the option **" @echo "** CFG_WEBSEARCH_USE_JSMATH_FOR_FORMATS in invenio.conf. **" @echo "***********************************************************" uninstall-jsmath-plugin: @rm -rvf ${prefix}/var/www/jsMath @echo "***********************************************************" @echo "** The jsMath plugin was successfully uninstalled. **" @echo "***********************************************************" install-jscalendar-plugin: @echo "***********************************************************" @echo "** Installing jsCalendar plugin, please wait... **" @echo "***********************************************************" rm -rf /tmp/invenio-jscalendar-plugin mkdir /tmp/invenio-jscalendar-plugin (cd /tmp/invenio-jscalendar-plugin && \ wget 'http://www.dynarch.com/static/jscalendar-1.0.zip' && \ unzip -u jscalendar-1.0.zip && \ mkdir -p ${prefix}/var/www/jsCalendar && \ cp jscalendar-1.0/img.gif ${prefix}/var/www/jsCalendar/jsCalendar.gif && \ cp jscalendar-1.0/calendar.js ${prefix}/var/www/jsCalendar/ && \ cp jscalendar-1.0/calendar-setup.js ${prefix}/var/www/jsCalendar/ && \ cp jscalendar-1.0/lang/calendar-en.js ${prefix}/var/www/jsCalendar/ && \ cp jscalendar-1.0/calendar-blue.css ${prefix}/var/www/jsCalendar/) rm -fr /tmp/invenio-jscalendar-plugin @echo "***********************************************************" @echo "** The jsCalendar plugin was successfully installed. **" @echo "***********************************************************" uninstall-jscalendar-plugin: @rm -rvf ${prefix}/var/www/jsCalendar @echo "***********************************************************" @echo "** The jsCalendar plugin was successfully uninstalled. **" @echo "***********************************************************" install-jquery-plugins: install-jquery-plugins @echo "***********************************************************" @echo "** Installing various jQuery plugins, please wait... **" @echo "***********************************************************" mkdir -p ${prefix}/var/www/js (cd ${prefix}/var/www/js && \ wget http://jqueryjs.googlecode.com/files/jquery-1.3.1.min.js && \ mv jquery-1.3.1.min.js jquery.min.js && \ wget http://jquery-ui.googlecode.com/svn/tags/latest/ui/minified/jquery.effects.core.min.js && \ wget http://jquery-ui.googlecode.com/svn/tags/latest/ui/minified/jquery.effects.highlight.min.js && \ wget http://jquery-ui.googlecode.com/svn/tags/1.7.3/ui/minified/ui.slider.min.js && \ wget http://jquery-ui.googlecode.com/svn/tags/1.7.3/ui/minified/ui.sortable.min.js && \ wget http://www.appelsiini.net/download/jquery.jeditable.mini.js && \ wget http://github.com/malsup/form/raw/master/jquery.form.js --no-check-certificate && \ wget http://jquery-multifile-plugin.googlecode.com/svn/trunk/jquery.MultiFile.pack.js && \ wget http://plugins.jquery.com/files/jquery.autogrow-1.2.2.zip && \ wget http://tablesorter.com/jquery.tablesorter.zip && \ wget http://www.uploadify.com/wp-content/uploads/Uploadify-v2.1.3.zip -O uploadify.zip && \ unzip jquery.tablesorter.zip && \ rm jquery.tablesorter.zip && \ unzip -u uploadify.zip && \ mv -T jquery.uploadify-v2.1.3 uploadify && \ mv uploadify/swfobject.js ./ && \ mv uploadify/cancel.png uploadify/uploadify.css uploadify/uploadify.allglyphs.swf uploadify/uploadify.fla uploadify/uploadify.swf ../img/ && \ mv uploadify/jquery.uploadify.v2.1.3.min.js ./jquery.uploadify.min.js && \ rm uploadify.zip && rm -r uploadify && \ unzip jquery.autogrow-1.2.2.zip jquery.autogrow.js && \ rm jquery.autogrow-1.2.2.zip && \ wget -O json2.js.tmp http://json.org/json2.js && \ grep -v 'alert.*IMPORTANT: Remove this line' json2.js.tmp > json2.js && \ rm json2.js.tmp && \ wget http://jquery-ui.googlecode.com/svn/tags/1.7.3/ui/minified/ui.datepicker.min.js && \ wget -O jquery.hotkeys.min.js http://js-hotkeys.googlecode.com/files/jquery.hotkeys-0.7.8-packed.js && \ wget http://plugins.jquery.com/files/jquery.treeview_3.zip && \ unzip jquery.treeview_3.zip && \ rm jquery.treeview_3.zip && \ wget http://plugins.jquery.com/files/jquery.ajaxPager.js_5.txt && \ mv jquery.ajaxPager.js_5.txt jquery.ajaxPager.js && \ wget http://jqueryui.com/download/jquery-ui-1.7.3.custom.zip && \ unzip jquery-ui-1.7.3.custom.zip development-bundle/ui/ui.core.js && \ mv development-bundle/ui/ui.core.js ui.core.js && \ rm -rf development-bundle && \ rm jquery-ui-1.7.3.custom.zip) mkdir -p ${prefix}/var/www/img && \ (cd ${prefix}/var/www/img && \ wget -r -np -nH --cut-dirs=4 -A "png,css" -P jquery-ui/themes http://jquery-ui.googlecode.com/svn/tags/1.7.3/themes/base/ && \ wget http://jquery-ui.googlecode.com/svn/tags/1.7.3/themes/redmond/jquery-ui.css && \ wget http://jquery-ui.googlecode.com/svn/tags/1.7.3/demos/images/calendar.gif && \ wget -r -np -nH --cut-dirs=5 -A "png" http://jquery-ui.googlecode.com/svn/tags/1.7.3/themes/redmond/images/) @echo "***********************************************************" @echo "** The jQuery plugins were successfully installed. **" @echo "***********************************************************" uninstall-jquery-plugins: (cd ${prefix}/var/www/js && \ rm -f jquery.min.js && \ rm -f jquery.effects.core.min.js && \ rm -f jquery.effects.highlight.min.js && \ rm -f jquery.MultiFile.pack.js && \ rm -f jquery.jeditable.mini.js && \ rm -f jquery.tablesorter.js && \ rm -f jquery.tablesorter.pager.js && \ rm -f ui.datepicker.min.js && \ rm -f jquery.autogrow.js && \ rm -f json2.js && \ rm -f jquery.uploadify.min.js && \ rm -f jquery.ui.slider.min.js && \ rm -f jquery.ui.sortable.min.js && \ rm -rf tablesorter && \ rm -f jquery.hotkeys.min.js && \ rm -rf jquery-treeview && \ rm -f jquery.ajaxPager.js && \ rm -f jquery.form.js && \ rm -f ui.core.js) && \ (cd ${prefix}/var/www/img && \ rm -f cancel.png uploadify.css uploadify.swf uploadify.allglyphs.swf uploadify.fla) @echo "***********************************************************" @echo "** The jquery plugins were successfully uninstalled. **" @echo "***********************************************************" install-fckeditor-plugin: @echo "***********************************************************" @echo "** Installing FCKeditor plugin, please wait... **" @echo "***********************************************************" rm -rf ${prefix}/lib/python/invenio/fckeditor/ rm -rf /tmp/invenio-fckeditor-plugin mkdir /tmp/invenio-fckeditor-plugin (cd /tmp/invenio-fckeditor-plugin && \ wget 'http://downloads.sourceforge.net/fckeditor/$(FCKEDITOR)' && \ unzip -u -d ${prefix}/var/www $(FCKEDITOR)) && \ mkdir -p ${prefix}/lib/python/invenio/fckeditor/editor/filemanager/connectors/py && \ mv -f ${prefix}/var/www/fckeditor/fckeditor.py ${prefix}/lib/python/invenio/fckeditor/ && \ mv -f ${prefix}/var/www/fckeditor/editor/filemanager/connectors/py/*.py ${prefix}/lib/python/invenio/fckeditor/editor/filemanager/connectors/py/ && \ rm -f ${prefix}/var/www/fckeditor/editor/filemanager/connectors/py/upload.py && \ rm -f ${prefix}/var/www/fckeditor/editor/filemanager/connectors/py/zope.py && \ find ${prefix}/lib/python/invenio/fckeditor -type d -exec touch {}/__init__.py \; && \ find ${prefix}/var/www/fckeditor/ -depth -name '_*' -exec rm -rf {} \; && \ rm -r ${prefix}/var/www/fckeditor/editor/filemanager/connectors && \ find ${prefix}/var/www/fckeditor/fckeditor* -maxdepth 0 ! -name "fckeditor.js" -exec rm -r {} \; && \ rm -fr /tmp/invenio-fckeditor-plugin @echo "* Installing Invenio-specific FCKeditor config..." (cd $(top_srcdir)/modules/webstyle/etc && make install) @echo "***********************************************************" @echo "** The FCKeditor plugin was successfully installed. **" @echo "** Please do not forget to properly set the option **" @echo "** CFG_WEBCOMMENT_USE_RICH_TEXT_EDITOR in invenio.conf. **" @echo "***********************************************************" uninstall-fckeditor-plugin: @rm -rvf ${prefix}/var/www/fckeditor @rm -rvf ${prefix}/lib/python/invenio/fckeditor @echo "***********************************************************" @echo "** The FCKeditor plugin was successfully uninstalled. **" @echo "***********************************************************" install-pdfa-helper-files: @echo "***********************************************************" @echo "** Installing PDF/A helper files, please wait... **" @echo "***********************************************************" wget 'http://cdsware.cern.ch/download/invenio-demo-site-files/ISOCoatedsb.icc' -O ${prefix}/etc/websubmit/file_converter_templates/ISOCoatedsb.icc @echo "***********************************************************" @echo "** The PDF/A helper files were successfully installed. **" @echo "***********************************************************" uninstall-pdfa-helper-files: rm -f ${prefix}/etc/websubmit/file_converter_templates/ISOCoatedsb.icc @echo "***********************************************************" @echo "** The PDF/A helper files were successfully uninstalled. **" @echo "***********************************************************" update-v0.3.0-tables update-v0.3.1-tables: echo "ALTER TABLE idxINDEXNAME CHANGE id_idxINDEX id_idxINDEX mediumint(9) unsigned NOT NULL FIRST;" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkMETHODNAME CHANGE id_rnkMETHOD id_rnkMETHOD mediumint(9) unsigned NOT NULL FIRST;" | ${prefix}/bin/dbexec echo "ALTER TABLE collectionname CHANGE id_collection id_collection mediumint(9) unsigned NOT NULL FIRST;" | ${prefix}/bin/dbexec echo "ALTER TABLE formatname CHANGE id_format id_format mediumint(9) unsigned NOT NULL FIRST;" | ${prefix}/bin/dbexec echo "ALTER TABLE fieldname CHANGE id_field id_field mediumint(9) unsigned NOT NULL FIRST;" | ${prefix}/bin/dbexec echo "INSERT INTO accACTION (id,name,description,allowedkeywords,optional) VALUES (NULL,'runbibrank','run BibRank','','no');" | ${prefix}/bin/dbexec echo "INSERT INTO accACTION (id,name,description,allowedkeywords,optional) VALUES (NULL,'cfgbibrank','configure BibRank','','no');" | ${prefix}/bin/dbexec update-v0.3.2-tables: echo "ALTER TABLE sbmCOLLECTION_sbmDOCTYPE CHANGE id_son id_son char(10) NOT NULL default '0';" | ${prefix}/bin/dbexec update-v0.3.3-tables: ${prefix}/bin/dbexec < $(top_srcdir)/modules/miscutil/sql/tabcreate.sql echo "ALTER TABLE flxLINKTYPEPARAMS CHANGE pname pname varchar(78) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkMETHOD DROP star_category_ranges;" | ${prefix}/bin/dbexec echo "DROP TABLE rnkSET;" | ${prefix}/bin/dbexec echo "ALTER TABLE schTASK CHANGE arguments arguments LONGTEXT;" | ${prefix}/bin/dbexec echo "ALTER TABLE schTASK CHANGE status status varchar(50);" | ${prefix}/bin/dbexec update-v0.5.0-tables: ${prefix}/bin/dbexec < $(top_srcdir)/modules/miscutil/sql/tabcreate.sql echo "ALTER TABLE session ADD INDEX uid (uid);" | ${prefix}/bin/dbexec echo "UPDATE idxINDEXNAME SET ln='cs' WHERE ln='cz';" | ${prefix}/bin/dbexec echo "UPDATE rnkMETHODNAME SET ln='cs' WHERE ln='cz';" | ${prefix}/bin/dbexec echo "UPDATE collectionname SET ln='cs' WHERE ln='cz';" | ${prefix}/bin/dbexec echo "UPDATE collection_portalbox SET ln='cs' WHERE ln='cz';" | ${prefix}/bin/dbexec echo "UPDATE formatname SET ln='cs' WHERE ln='cz';" | ${prefix}/bin/dbexec echo "UPDATE fieldname SET ln='cs' WHERE ln='cz';" | ${prefix}/bin/dbexec echo "UPDATE idxINDEXNAME SET ln='sv' WHERE ln='se';" | ${prefix}/bin/dbexec echo "UPDATE rnkMETHODNAME SET ln='sv' WHERE ln='se';" | ${prefix}/bin/dbexec echo "UPDATE collectionname SET ln='sv' WHERE ln='se';" | ${prefix}/bin/dbexec echo "UPDATE collection_portalbox SET ln='sv' WHERE ln='se';" | ${prefix}/bin/dbexec echo "UPDATE formatname SET ln='sv' WHERE ln='se';" | ${prefix}/bin/dbexec echo "UPDATE fieldname SET ln='sv' WHERE ln='se';" | ${prefix}/bin/dbexec update-v0.7.1-tables: echo "DROP TABLE oaiHARVEST;" | ${prefix}/bin/dbexec ${prefix}/bin/dbexec < $(top_srcdir)/modules/miscutil/sql/tabcreate.sql echo "INSERT INTO accACTION (id,name,description,allowedkeywords,optional) VALUES (NULL,'cfgbibharvest','configure BibHarvest','','no');" | ${prefix}/bin/dbexec echo "INSERT INTO accACTION (id,name,description,allowedkeywords,optional) VALUES (NULL,'runoaiharvest','run BibHarvest oaiharvest','','no');" | ${prefix}/bin/dbexec echo "INSERT INTO accACTION (id,name,description,allowedkeywords,optional) VALUES (NULL,'cfgwebcomment','configure WebComment','','no');" | ${prefix}/bin/dbexec echo "INSERT INTO accACTION (id,name,description,allowedkeywords,optional) VALUES (NULL,'runoaiarchive','run BibHarvest oaiarchive','','no');" | ${prefix}/bin/dbexec echo "INSERT INTO accACTION (id,name,description,allowedkeywords,optional) VALUES (NULL,'runbibedit','run BibEdit','','no');" | ${prefix}/bin/dbexec echo "ALTER TABLE user ADD nickname varchar(255) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE user ADD last_login datetime NOT NULL default '0000-00-00 00:00:00';" | ${prefix}/bin/dbexec echo "ALTER TABLE user ADD INDEX nickname (nickname);" | ${prefix}/bin/dbexec echo "ALTER TABLE sbmFIELD CHANGE subname subname varchar(13) default NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE user_query_basket CHANGE alert_name alert_name varchar(30) NOT NULL default '';" | ${prefix}/bin/dbexec echo "TRUNCATE TABLE session;" | ${prefix}/bin/dbexec @echo "**********************************************************" @echo "** Do not forget to run the basket migration now: **" @echo "** @PYTHON@ modules/webbasket/lib/webbasket_migration_kit.py " @echo "** Please see the RELEASE-NOTES for details. **" @echo "**********************************************************" @echo "INSERT INTO oaiARCHIVE (id, setName, setSpec, setDescription, setDefinition, setRecList) SELECT id, setName, setSpec, CONCAT_WS('', setDescription), setDefinition, setRecList FROM oaiSET;" update-v0.90.0-tables: ${prefix}/bin/dbexec < $(top_srcdir)/modules/miscutil/sql/tabcreate.sql echo "ALTER TABLE format ADD COLUMN (description varchar(255) default '');" | ${prefix}/bin/dbexec echo "ALTER TABLE format ADD COLUMN (content_type varchar(255) default '');" | ${prefix}/bin/dbexec update-v0.90.1-tables: ${prefix}/bin/dbexec < $(top_srcdir)/modules/miscutil/sql/tabcreate.sql echo "ALTER TABLE schTASK ADD INDEX status (status);" | ${prefix}/bin/dbexec echo "ALTER TABLE schTASK ADD INDEX runtime (runtime);" | ${prefix}/bin/dbexec echo "ALTER TABLE sbmCATEGORIES ADD COLUMN score TINYINT UNSIGNED NOT NULL DEFAULT 0;" | ${prefix}/bin/dbexec echo "ALTER TABLE sbmCATEGORIES ADD PRIMARY KEY (doctype, sname);" | ${prefix}/bin/dbexec echo "ALTER TABLE sbmCATEGORIES ADD KEY doctype (doctype);" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiHARVEST ADD COLUMN setspecs TEXT NOT NULL DEFAULT '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE setDescription setDescription text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE p1 p1 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE f1 f1 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE m1 m1 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE p2 p2 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE f2 f2 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE m2 m2 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE p3 p3 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE f3 f3 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiARCHIVE CHANGE m3 m3 text NOT NULL default '';" | ${prefix}/bin/dbexec echo "UPDATE bibdoc SET status=0 WHERE status='';" | ${prefix}/bin/dbexec echo "UPDATE bibdoc SET status=1 WHERE status='deleted';" | ${prefix}/bin/dbexec echo "ALTER TABLE fmtKNOWLEDGEBASES add COLUMN kbtype char default NULL;" | ${prefix}/bin/dbexec update-v0.92.0-tables: echo "UPDATE bibdoc SET status=0 WHERE status='';" | ${prefix}/bin/dbexec echo "UPDATE bibdoc SET status=1 WHERE status='deleted';" | ${prefix}/bin/dbexec echo "ALTER TABLE schTASK CHANGE arguments arguments mediumblob;" | ${prefix}/bin/dbexec echo "UPDATE user SET note=1 WHERE nickname='admin' AND note IS NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE usergroup CHANGE name name varchar(255) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE usergroup ADD login_method varchar(255) NOT NULL default 'INTERNAL';" | ${prefix}/bin/dbexec echo "ALTER TABLE usergroup ADD UNIQUE KEY login_method_name (login_method(70), name);" | ${prefix}/bin/dbexec echo "ALTER TABLE user CHANGE settings settings blob default NULL;" | ${prefix}/bin/dbexec echo "INSERT INTO sbmALLFUNCDESCR VALUES ('Get_Recid', 'This function gets the recid for a document with a given report-number (as stored in the global variable rn).');" | ${prefix}/bin/dbexec update-v0.92.1-tables: echo "DROP TABLE rnkCITATIONDATA;" | ${prefix}/bin/dbexec ${prefix}/bin/dbexec < $(top_srcdir)/modules/miscutil/sql/tabcreate.sql echo "UPDATE bibdoc SET status='DELETED' WHERE status='1';" | ${prefix}/bin/dbexec echo "UPDATE bibdoc SET status='' WHERE status='0';" | ${prefix}/bin/dbexec echo "ALTER TABLE bibrec ADD KEY creation_date (creation_date);" | ${prefix}/bin/dbexec echo "ALTER TABLE bibrec ADD KEY modification_date (modification_date);" | ${prefix}/bin/dbexec echo "ALTER TABLE bibdoc ADD KEY creation_date (creation_date);" | ${prefix}/bin/dbexec echo "ALTER TABLE bibdoc ADD KEY modification_date (modification_date);" | ${prefix}/bin/dbexec echo "ALTER TABLE bibdoc ADD KEY docname (docname);" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiHARVEST CHANGE postprocess postprocess varchar(20) NOT NULL default 'h';" | ${prefix}/bin/dbexec echo "ALTER TABLE oaiHARVEST ADD COLUMN bibfilterprogram varchar(255) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE idxINDEXNAME CHANGE ln ln char(5) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE idxINDEX ADD COLUMN stemming_language VARCHAR(10) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkMETHODNAME CHANGE ln ln char(5) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkDOWNLOADS CHANGE id_bibdoc id_bibdoc mediumint(9) unsigned default NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkDOWNLOADS CHANGE file_format file_format varchar(10) NULL default NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE collectionname CHANGE ln ln char(5) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE collection_portalbox CHANGE ln ln char(5) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE format ADD COLUMN visibility TINYINT NOT NULL default 1;" | ${prefix}/bin/dbexec echo "ALTER TABLE formatname CHANGE ln ln char(5) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE fieldname CHANGE ln ln char(5) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE accROLE ADD COLUMN firerole_def_ser blob NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE accROLE ADD COLUMN firerole_def_src text NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE user_accROLE ADD COLUMN expiration datetime NOT NULL default '9999-12-31 23:59:59';" | ${prefix}/bin/dbexec echo "ALTER TABLE user DROP INDEX id, ADD PRIMARY KEY id (id);" | ${prefix}/bin/dbexec echo -e 'from invenio.dbquery import run_sql;\ map(lambda index_id: run_sql("ALTER TABLE idxPHRASE%02dF CHANGE term term TEXT NULL DEFAULT NULL, DROP INDEX term, ADD INDEX term (term (50))" % index_id[0]), run_sql("select id from idxINDEX"))' | $(PYTHON) echo "INSERT INTO rnkCITATIONDATA VALUES (1,'citationdict','','');" | ${prefix}/bin/dbexec echo "INSERT INTO rnkCITATIONDATA VALUES (2,'reversedict','','');" | ${prefix}/bin/dbexec echo "INSERT INTO rnkCITATIONDATA VALUES (3,'selfcitdict','','');" | ${prefix}/bin/dbexec update-v0.99.0-tables: ${prefix}/bin/dbexec < $(top_srcdir)/modules/miscutil/sql/tabcreate.sql echo "ALTER TABLE bibdoc ADD COLUMN more_info mediumblob NULL default NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE schTASK ADD COLUMN priority tinyint(4) NOT NULL default 0;" | ${prefix}/bin/dbexec echo "ALTER TABLE schTASK ADD KEY priority (priority);" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkCITATIONDATA DROP PRIMARY KEY;" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkCITATIONDATA ADD PRIMARY KEY (id);" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkCITATIONDATA CHANGE id id mediumint(8) unsigned NOT NULL auto_increment;" | ${prefix}/bin/dbexec echo "ALTER TABLE rnkCITATIONDATA ADD UNIQUE KEY object_name (object_name);" | ${prefix}/bin/dbexec echo "ALTER TABLE sbmPARAMETERS CHANGE value value text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE sbmAPPROVAL ADD note text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE hstDOCUMENT CHANGE docsize docsize bigint(15) unsigned NOT NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtACTIONHISTORY CHANGE client_host client_host int(10) unsigned default NULL;" | ${prefix}/bin/dbexec update-v0.99.1-tables: @echo "Nothing to do; table structure did not change between v0.99.1 and v0.99.2." update-v0.99.2-tables: echo "RENAME TABLE oaiARCHIVE TO oaiREPOSITORY;" | ${prefix}/bin/dbexec ${prefix}/bin/dbexec < $(top_srcdir)/modules/miscutil/sql/tabcreate.sql echo "INSERT INTO knwKB (id,name,description,kbtype) SELECT id,name,description,'' FROM fmtKNOWLEDGEBASES;" | ${prefix}/bin/dbexec echo "INSERT INTO knwKBRVAL (id,m_key,m_value,id_knwKB) SELECT id,m_key,m_value,id_fmtKNOWLEDGEBASES FROM fmtKNOWLEDGEBASEMAPPINGS;" | ${prefix}/bin/dbexec echo "ALTER TABLE sbmPARAMETERS CHANGE name name varchar(40) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE bibdoc CHANGE docname docname varchar(250) COLLATE utf8_bin NOT NULL default 'file';" | ${prefix}/bin/dbexec echo "ALTER TABLE bibdoc CHANGE status status text NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE bibdoc ADD COLUMN text_extraction_date datetime NOT NULL default '0000-00-00';" | ${prefix}/bin/dbexec echo "ALTER TABLE collection DROP COLUMN restricted;" | ${prefix}/bin/dbexec echo "ALTER TABLE schTASK CHANGE host host varchar(255) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE hstTASK CHANGE host host varchar(255) NOT NULL default '';" | ${prefix}/bin/dbexec echo "ALTER TABLE bib85x DROP INDEX kv, ADD INDEX kv (value(100));" | ${prefix}/bin/dbexec echo "UPDATE clsMETHOD SET location='http://cdsware.cern.ch/download/invenio-demo-site-files/HEP.rdf' WHERE name='HEP' AND location='';" | ${prefix}/bin/dbexec echo "UPDATE clsMETHOD SET location='http://cdsware.cern.ch/download/invenio-demo-site-files/NASA-subjects.rdf' WHERE name='NASA-subjects' AND location='';" | ${prefix}/bin/dbexec echo "UPDATE accACTION SET name='runoairepository', description='run oairepositoryupdater task' WHERE name='runoaiarchive';" | ${prefix}/bin/dbexec echo "UPDATE accACTION SET name='cfgoaiharvest', description='configure OAI Harvest' WHERE name='cfgbibharvest';" | ${prefix}/bin/dbexec echo "ALTER TABLE accARGUMENT CHANGE value value varchar(255);" | ${prefix}/bin/dbexec echo "UPDATE accACTION SET allowedkeywords='doctype,act,categ' WHERE name='submit';" | ${prefix}/bin/dbexec echo "INSERT INTO accARGUMENT(keyword,value) VALUES ('categ','*');" | ${prefix}/bin/dbexec echo "INSERT INTO accROLE_accACTION_accARGUMENT(id_accROLE,id_accACTION,id_accARGUMENT,argumentlistid) SELECT DISTINCT raa.id_accROLE,raa.id_accACTION,accARGUMENT.id,raa.argumentlistid FROM accROLE_accACTION_accARGUMENT as raa JOIN accACTION on id_accACTION=accACTION.id,accARGUMENT WHERE accACTION.name='submit' and accARGUMENT.keyword='categ' and accARGUMENT.value='*';" | ${prefix}/bin/dbexec echo "UPDATE accACTION SET allowedkeywords='name,with_editor_rights' WHERE name='cfgwebjournal';" | ${prefix}/bin/dbexec echo "INSERT INTO accARGUMENT(keyword,value) VALUES ('with_editor_rights','yes');" | ${prefix}/bin/dbexec echo "INSERT INTO accROLE_accACTION_accARGUMENT(id_accROLE,id_accACTION,id_accARGUMENT,argumentlistid) SELECT DISTINCT raa.id_accROLE,raa.id_accACTION,accARGUMENT.id,raa.argumentlistid FROM accROLE_accACTION_accARGUMENT as raa JOIN accACTION on id_accACTION=accACTION.id,accARGUMENT WHERE accACTION.name='cfgwebjournal' and accARGUMENT.keyword='with_editor_rights' and accARGUMENT.value='yes';" | ${prefix}/bin/dbexec echo "ALTER TABLE bskEXTREC CHANGE id id int(15) unsigned NOT NULL auto_increment;" | ${prefix}/bin/dbexec echo "ALTER TABLE bskEXTREC ADD external_id int(15) NOT NULL default '0';" | ${prefix}/bin/dbexec echo "ALTER TABLE bskEXTREC ADD collection_id int(15) unsigned NOT NULL default '0';" | ${prefix}/bin/dbexec echo "ALTER TABLE bskEXTREC ADD original_url text;" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtRECORDCOMMENT ADD status char(2) NOT NULL default 'ok';" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtRECORDCOMMENT ADD KEY status (status);" | ${prefix}/bin/dbexec echo "INSERT INTO sbmALLFUNCDESCR VALUES ('Move_Photos_to_Storage','Attach/edit the pictures uploaded with the \"create_photos_manager_interface()\" function');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFIELDDESC VALUES ('Upload_Photos',NULL,'','R',NULL,NULL,NULL,NULL,NULL,'\"\"\"\r\nThis is an example of element that creates a photos upload interface.\r\nClone it, customize it and integrate it into your submission. Then add function \r\n\'Move_Photos_to_Storage\' to your submission functions list, in order for files \r\nuploaded with this interface to be attached to the record. More information in \r\nthe WebSubmit admin guide.\r\n\"\"\"\r\n\r\nfrom invenio.websubmit_functions.ParamFile import ParamFromFile\r\nfrom invenio.websubmit_functions.Move_Photos_to_Storage import read_param_file, create_photos_manager_interface, get_session_id\r\n\r\n# Retrieve session id\r\ntry:\r\n # User info is defined only in MBI/MPI actions...\r\n session_id = get_session_id(None, uid, user_info) \r\nexcept:\r\n session_id = get_session_id(req, uid, {})\r\n\r\n# Retrieve context\r\nindir = curdir.split(\'/\')[-3]\r\ndoctype = curdir.split(\'/\')[-2]\r\naccess = curdir.split(\'/\')[-1]\r\n\r\n# Get the record ID, if any\r\nsysno = ParamFromFile(\"%s/%s\" % (curdir,\'SN\')).strip()\r\n\r\n\"\"\"\r\nModify below the configuration of the photos manager interface.\r\nNote: \'can_reorder_photos\' parameter is not yet fully taken into consideration\r\n\r\nDocumentation of the function is available by running:\r\necho -e \'from invenio.websubmit_functions.Move_Photos_to_Storage import create_photos_manager_interface as f\\nprint f.__doc__\' | python\r\n\"\"\"\r\ntext += create_photos_manager_interface(sysno, session_id, uid,\r\n doctype, indir, curdir, access,\r\n can_delete_photos=True,\r\n can_reorder_photos=True,\r\n can_upload_photos=True,\r\n editor_width=700,\r\n editor_height=400,\r\n initial_slider_value=100,\r\n max_slider_value=200,\r\n min_slider_value=80)','0000-00-00','0000-00-00',NULL,NULL,0);" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Photos_to_Storage','iconsize');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFIELDDESC VALUES ('Upload_Files',NULL,'','R',NULL,NULL,NULL,NULL,NULL,'\"\"\"\r\nThis is an example of element that creates a file upload interface.\r\nClone it, customize it and integrate it into your submission. Then add function \r\n\'Move_Uploaded_Files_to_Storage\' to your submission functions list, in order for files \r\nuploaded with this interface to be attached to the record. More information in \r\nthe WebSubmit admin guide.\r\n\"\"\"\r\nfrom invenio.websubmit_managedocfiles import create_file_upload_interface\r\nfrom invenio.websubmit_functions.Shared_Functions import ParamFromFile\r\n\r\nindir = ParamFromFile(os.path.join(curdir, \'indir\'))\r\ndoctype = ParamFromFile(os.path.join(curdir, \'doctype\'))\r\naccess = ParamFromFile(os.path.join(curdir, \'access\'))\r\ntry:\r\n sysno = int(ParamFromFile(os.path.join(curdir, \'SN\')).strip())\r\nexcept:\r\n sysno = -1\r\nln = ParamFromFile(os.path.join(curdir, \'ln\'))\r\n\r\n\"\"\"\r\nRun the following to get the list of parameters of function \'create_file_upload_interface\':\r\necho -e \'from invenio.websubmit_managedocfiles import create_file_upload_interface as f\\nprint f.__doc__\' | python\r\n\"\"\"\r\ntext = create_file_upload_interface(recid=sysno,\r\n print_outside_form_tag=False,\r\n include_headers=True,\r\n ln=ln,\r\n doctypes_and_desc=[(\'main\',\'Main document\'),\r\n (\'additional\',\'Figure, schema, etc.\')],\r\n can_revise_doctypes=[\'*\'],\r\n can_describe_doctypes=[\'main\'],\r\n can_delete_doctypes=[\'additional\'],\r\n can_rename_doctypes=[\'main\'],\r\n sbm_indir=indir, sbm_doctype=doctype, sbm_access=access)[1]\r\n','0000-00-00','0000-00-00',NULL,NULL,0);" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Uploaded_Files_to_Storage','forceFileRevision');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmALLFUNCDESCR VALUES ('Create_Upload_Files_Interface','Display generic interface to add/revise/delete files. To be used before function \"Move_Uploaded_Files_to_Storage\"');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmALLFUNCDESCR VALUES ('Move_Uploaded_Files_to_Storage','Attach files uploaded with \"Create_Upload_Files_Interface\"')" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Revised_Files_to_Storage','elementNameToDoctype');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Revised_Files_to_Storage','createIconDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Revised_Files_to_Storage','createRelatedFormats');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Revised_Files_to_Storage','iconsize');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Revised_Files_to_Storage','keepPreviousVersionDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmALLFUNCDESCR VALUES ('Move_Revised_Files_to_Storage','Revise files initially uploaded with \"Move_Files_to_Storage\"')" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','maxsize');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','minsize');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','doctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','restrictions');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canDeleteDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canReviseDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canDescribeDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canCommentDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canKeepDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canAddFormatDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canRestrictDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canRenameDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','canNameNewFiles');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','createRelatedFormats');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','keepDefault');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','showLinks');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','fileLabel');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','filenameLabel');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','descriptionLabel');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','commentLabel');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','restrictionLabel');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','startDoc');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','endDoc');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','defaultFilenameDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Create_Upload_Files_Interface','maxFilesDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Uploaded_Files_to_Storage','iconsize');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Uploaded_Files_to_Storage','createIconDoctypes');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Report_Number_Generation','nblength');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Second_Report_Number_Generation','2nd_nb_length');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmALLFUNCDESCR VALUES ('Move_FCKeditor_Files_to_Storage','Transfer files attached to the record with the FCKeditor');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_FCKeditor_Files_to_Storage','input_fields');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Stamp_Uploaded_Files','layer');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Stamp_Replace_Single_File_Approval','layer');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Stamp_Replace_Single_File_Approval','switch_file');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Stamp_Uploaded_Files','switch_file');" | ${prefix}/bin/dbexec echo "INSERT INTO sbmFUNDESC VALUES ('Move_Files_to_Storage','paths_and_restrictions');" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtRECORDCOMMENT ADD round_name varchar(255) NOT NULL default ''" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtRECORDCOMMENT ADD restriction varchar(50) NOT NULL default ''" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtRECORDCOMMENT ADD in_reply_to_id_cmtRECORDCOMMENT int(15) unsigned NOT NULL default '0'" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtRECORDCOMMENT ADD KEY in_reply_to_id_cmtRECORDCOMMENT (in_reply_to_id_cmtRECORDCOMMENT);" | ${prefix}/bin/dbexec echo "ALTER TABLE bskRECORDCOMMENT ADD in_reply_to_id_bskRECORDCOMMENT int(15) unsigned NOT NULL default '0'" | ${prefix}/bin/dbexec echo "ALTER TABLE bskRECORDCOMMENT ADD KEY in_reply_to_id_bskRECORDCOMMENT (in_reply_to_id_bskRECORDCOMMENT);" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtRECORDCOMMENT ADD reply_order_cached_data blob NULL default NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE bskRECORDCOMMENT ADD reply_order_cached_data blob NULL default NULL;" | ${prefix}/bin/dbexec echo "ALTER TABLE cmtRECORDCOMMENT ADD INDEX (reply_order_cached_data(40));" | ${prefix}/bin/dbexec echo "ALTER TABLE bskRECORDCOMMENT ADD INDEX (reply_order_cached_data(40));" | ${prefix}/bin/dbexec echo -e 'from invenio.webcommentadminlib import migrate_comments_populate_threads_index;\ migrate_comments_populate_threads_index()' | $(PYTHON) + echo -e 'from invenio.access_control_firerole import repair_role_definitions;\ + repair_role_definitions()' | $(PYTHON) CLEANFILES = *~ *.pyc *.tmp diff --git a/config/invenio.conf b/config/invenio.conf index fe4bd38c5..2e85eaa09 100644 --- a/config/invenio.conf +++ b/config/invenio.conf @@ -1,1164 +1,1168 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. ################################################### ## About 'invenio.conf' and 'invenio-local.conf' ## ################################################### ## The 'invenio.conf' file contains the vanilla default configuration ## parameters of a Invenio installation, as coming out of the ## distribution. The file should be self-explanatory. Once installed ## in its usual location (usually /opt/invenio/etc), you could in ## principle go ahead and change the values according to your local ## needs, but this is not advised. ## ## If you would like to customize some of these parameters, you should ## rather create a file named 'invenio-local.conf' in the same ## directory where 'invenio.conf' lives and you should write there ## only the customizations that you want to be different from the ## vanilla defaults. ## ## Here is a realistic, minimalist, yet production-ready example of ## what you would typically put there: ## ## $ cat /opt/invenio/etc/invenio-local.conf ## [Invenio] ## CFG_SITE_NAME = John Doe's Document Server ## CFG_SITE_NAME_INTL_fr = Serveur des Documents de John Doe ## CFG_SITE_URL = http://your.site.com ## CFG_SITE_SECURE_URL = https://your.site.com ## CFG_SITE_ADMIN_EMAIL = john.doe@your.site.com ## CFG_SITE_SUPPORT_EMAIL = john.doe@your.site.com ## CFG_WEBALERT_ALERT_ENGINE_EMAIL = john.doe@your.site.com ## CFG_WEBCOMMENT_ALERT_ENGINE_EMAIL = john.doe@your.site.com ## CFG_WEBCOMMENT_DEFAULT_MODERATOR = john.doe@your.site.com ## CFG_DATABASE_HOST = localhost ## CFG_DATABASE_NAME = invenio ## CFG_DATABASE_USER = invenio ## CFG_DATABASE_PASS = my123p$ss ## ## You should override at least the parameters mentioned above and the ## parameters mentioned in the `Part 1: Essential parameters' below in ## order to define some very essential runtime parameters such as the ## name of your document server (CFG_SITE_NAME and ## CFG_SITE_NAME_INTL_*), the visible URL of your document server ## (CFG_SITE_URL and CFG_SITE_SECURE_URL), the email address of the ## local Invenio administrator, comment moderator, and alert engine ## (CFG_SITE_SUPPORT_EMAIL, CFG_SITE_ADMIN_EMAIL, etc), and last but ## not least your database credentials (CFG_DATABASE_*). ## ## The Invenio system will then read both the default invenio.conf ## file and your customized invenio-local.conf file and it will ## override any default options with the ones you have specified in ## your local file. This cascading of configuration parameters will ## ease your future upgrades. [Invenio] ################################### ## Part 1: Essential parameters ## ################################### ## This part defines essential Invenio internal parameters that ## everybody should override, like the name of the server or the email ## address of the local Invenio administrator. ## CFG_DATABASE_* - specify which MySQL server to use, the name of the ## database to use, and the database access credentials. CFG_DATABASE_HOST = localhost CFG_DATABASE_PORT = 3306 CFG_DATABASE_NAME = invenio CFG_DATABASE_USER = invenio CFG_DATABASE_PASS = my123p$ss ## CFG_SITE_URL - specify URL under which your installation will be ## visible. For example, use "http://your.site.com". Do not leave ## trailing slash. CFG_SITE_URL = http://localhost ## CFG_SITE_SECURE_URL - specify secure URL under which your ## installation secure pages such as login or registration will be ## visible. For example, use "https://your.site.com". Do not leave ## trailing slash. If you don't plan on using HTTPS, then you may ## leave this empty. CFG_SITE_SECURE_URL = https://localhost ## CFG_SITE_NAME -- the visible name of your Invenio installation. CFG_SITE_NAME = Atlantis Institute of Fictive Science ## CFG_SITE_NAME_INTL -- the international versions of CFG_SITE_NAME ## in various languages. (See also CFG_SITE_LANGS below.) CFG_SITE_NAME_INTL_en = Atlantis Institute of Fictive Science CFG_SITE_NAME_INTL_fr = Atlantis Institut des Sciences Fictives CFG_SITE_NAME_INTL_de = Atlantis Institut der fiktiven Wissenschaft CFG_SITE_NAME_INTL_es = Atlantis Instituto de la Ciencia Fictive CFG_SITE_NAME_INTL_ca = Institut Atlantis de Ciència Fictícia CFG_SITE_NAME_INTL_pt = Instituto Atlantis de Ciência Fictícia CFG_SITE_NAME_INTL_it = Atlantis Istituto di Scienza Fittizia CFG_SITE_NAME_INTL_ru = Атлантис Институт фиктивных Наук CFG_SITE_NAME_INTL_sk = Atlantis Inštitút Fiktívnych Vied CFG_SITE_NAME_INTL_cs = Atlantis Institut Fiktivních Věd CFG_SITE_NAME_INTL_no = Atlantis Institutt for Fiktiv Vitenskap CFG_SITE_NAME_INTL_sv = Atlantis Institut för Fiktiv Vetenskap CFG_SITE_NAME_INTL_el = Ινστιτούτο Φανταστικών Επιστημών Ατλαντίδος CFG_SITE_NAME_INTL_uk = Інститут вигаданих наук в Атлантісі CFG_SITE_NAME_INTL_ja = Fictive 科学のAtlantis の協会 CFG_SITE_NAME_INTL_pl = Instytut Fikcyjnej Nauki Atlantis CFG_SITE_NAME_INTL_bg = Институт за фиктивни науки Атлантис CFG_SITE_NAME_INTL_hr = Institut Fiktivnih Znanosti Atlantis CFG_SITE_NAME_INTL_zh_CN = 阿特兰提斯虚拟科学学院 CFG_SITE_NAME_INTL_zh_TW = 阿特蘭提斯虛擬科學學院 CFG_SITE_NAME_INTL_hu = Kitalált Tudományok Atlantiszi Intézete CFG_SITE_NAME_INTL_af = Atlantis Instituut van Fiktiewe Wetenskap CFG_SITE_NAME_INTL_gl = Instituto Atlantis de Ciencia Fictive CFG_SITE_NAME_INTL_ro = Institutul Atlantis al Ştiinţelor Fictive CFG_SITE_NAME_INTL_rw = Atlantis Ishuri Rikuru Ry'ubuhanga CFG_SITE_NAME_INTL_ka = ატლანტიდის ფიქტიური მეცნიერების ინსტიტუტი ## CFG_SITE_LANG -- the default language of the interface: ' CFG_SITE_LANG = en ## CFG_SITE_LANGS -- list of all languages the user interface should ## be available in, separated by commas. The order specified below ## will be respected on the interface pages. A good default would be ## to use the alphabetical order. Currently supported languages ## include Afrikaans, Bulgarian, Catalan, Czech, German, Georgian, ## Greek, English, Spanish, French, Croatian, Hungarian, Galician, ## Italian, Japanese, Kinyarwanda, Norwegian, Polish, Portuguese, ## Romanian, Russian, Slovak, Swedish, Ukrainian, Chinese (China), ## Chinese (Taiwan), so that the eventual maximum you can currently ## select is "af,bg,ca,cs,de,el,en,es,fr,hr,gl,it,ka,rw,hu,ja,no,pl,pt,ro,ru,sk,sv,uk,zh_CN,zh_TW". CFG_SITE_LANGS = af,bg,ca,cs,de,el,en,es,fr,hr,gl,it,ka,rw,hu,ja,no,pl,pt,ro,ru,sk,sv,uk,zh_CN,zh_TW ## CFG_SITE_SUPPORT_EMAIL -- the email address of the support team for ## this installation: CFG_SITE_SUPPORT_EMAIL = info@invenio-software.org ## CFG_SITE_ADMIN_EMAIL -- the email address of the 'superuser' for ## this installation. Enter your email address below and login with ## this address when using Invenio inistration modules. You ## will then be automatically recognized as superuser of the system. CFG_SITE_ADMIN_EMAIL = info@invenio-software.org ## CFG_SITE_EMERGENCY_PHONE_NUMBERS -- list of mobile phone numbers to ## which an sms should be sent in case of emergency (e.g. bibsched queue ## has been stopped because of an error). ## Note that in order to use this function, if CFG_CERN_SITE is set to 0, ## the function send_sms in errorlib should be reimplemented. CFG_SITE_EMERGENCY_PHONE_NUMBERS = ## CFG_SITE_ADMIN_EMAIL_EXCEPTIONS -- set this to 0 if you do not want ## to receive any captured exception via email to CFG_SITE_ADMIN_EMAIL ## address. Captured exceptions will still be available in ## var/log/invenio.err file. Set this to 1 if you want to receive ## some of the captured exceptions (this depends on the actual place ## where the exception is captured). Set this to 2 if you want to ## receive all captured exceptions. CFG_SITE_ADMIN_EMAIL_EXCEPTIONS = 1 ## CFG_CERN_SITE -- do we want to enable CERN-specific code? ## Put "1" for "yes" and "0" for "no". CFG_CERN_SITE = 0 ## CFG_INSPIRE_SITE -- do we want to enable INSPIRE-specific code? ## Put "1" for "yes" and "0" for "no". CFG_INSPIRE_SITE = 0 ## CFG_ADS_SITE -- do we want to enable ADS-specific code? ## Put "1" for "yes" and "0" for "no". CFG_ADS_SITE = 0 +## CFG_OPENAIRE_SITE -- do we want to enable OpenAIRE-specific code? +## Put "1" for "yes" and "0" for "no". +CFG_OPENAIRE_SITE = 0 + ## CFG_DEVEL_SITE -- is this a development site? If it is, you might ## prefer that it does not do certain things. For example, you might ## not want WebSubmit to send certain emails or trigger certain ## processes on a development site. ## Put "1" for "yes" (this is a development site) or "0" for "no" ## (this isn't a development site.) CFG_DEVEL_SITE = 0 ################################ ## Part 2: Web page style ## ################################ ## The variables affecting the page style. The most important one is ## the 'template skin' you would like to use and the obfuscation mode ## for your email addresses. Please refer to the WebStyle Admin Guide ## for more explanation. The other variables are listed here mostly ## for backwards compatibility purposes only. ## CFG_WEBSTYLE_TEMPLATE_SKIN -- what template skin do you want to ## use? CFG_WEBSTYLE_TEMPLATE_SKIN = default ## CFG_WEBSTYLE_EMAIL_ADDRESSES_OBFUSCATION_MODE. How do we "protect" ## email addresses from undesired automated email harvesters? This ## setting will not affect 'support' and 'admin' emails. ## NOTE: there is no ultimate solution to protect against email ## harvesting. All have drawbacks and can more or less be ## circumvented. Choose you preferred mode ([t] means "transparent" ## for the user): ## -1: hide all emails. ## [t] 0 : no protection, email returned as is. ## foo@example.com => foo@example.com ## 1 : basic email munging: replaces @ by [at] and . by [dot] ## foo@example.com => foo [at] example [dot] com ## [t] 2 : transparent name mangling: characters are replaced by ## equivalent HTML entities. ## foo@example.com => foo@example.com ## [t] 3 : javascript insertion. Requires Javascript enabled on client ## side. ## 4 : replaces @ and . characters by gif equivalents. ## foo@example.com => foo [at] example [dot] com CFG_WEBSTYLE_EMAIL_ADDRESSES_OBFUSCATION_MODE = 2 ## CFG_WEBSTYLE_INSPECT_TEMPLATES -- Do we want to debug all template ## functions so that they would return HTML results wrapped in ## comments indicating which part of HTML page was created by which ## template function? Useful only for debugging Pythonic HTML ## template. See WebStyle Admin Guide for more information. CFG_WEBSTYLE_INSPECT_TEMPLATES = 0 ## (deprecated) CFG_WEBSTYLE_CDSPAGEBOXLEFTTOP -- eventual global HTML ## left top box: CFG_WEBSTYLE_CDSPAGEBOXLEFTTOP = ## (deprecated) CFG_WEBSTYLE_CDSPAGEBOXLEFTBOTTOM -- eventual global ## HTML left bottom box: CFG_WEBSTYLE_CDSPAGEBOXLEFTBOTTOM = ## (deprecated) CFG_WEBSTYLE_CDSPAGEBOXRIGHTTOP -- eventual global ## HTML right top box: CFG_WEBSTYLE_CDSPAGEBOXRIGHTTOP = ## (deprecated) CFG_WEBSTYLE_CDSPAGEBOXRIGHTBOTTOM -- eventual global ## HTML right bottom box: CFG_WEBSTYLE_CDSPAGEBOXRIGHTBOTTOM = ## CFG_WEBSTYLE_HTTP_STATUS_ALERT_LIST -- when certain HTTP status ## codes are raised to the WSGI handler, the corresponding exceptions ## and error messages can be sent to the system administrator for ## inspecting. This is useful to detect and correct errors. The ## variable represents a comma-separated list of HTTP statuses that ## should alert admin. Wildcards are possible. If the status is ## followed by an "r", it means that a referer is required to exist ## (useful to distinguish broken known links from URL typos when 404 ## errors are raised). CFG_WEBSTYLE_HTTP_STATUS_ALERT_LIST = 404r,400,5*,41* ################################## ## Part 3: WebSearch parameters ## ################################## ## This section contains some configuration parameters for WebSearch ## module. Please note that WebSearch is mostly configured on ## run-time via its WebSearch Admin web interface. The parameters ## below are the ones that you do not probably want to modify very ## often during the runtime. (Note that you may modify them ## afterwards too, though.) ## CFG_WEBSEARCH_SEARCH_CACHE_SIZE -- how many queries we want to ## cache in memory per one Apache httpd process? This cache is used ## mainly for "next/previous page" functionality, but it caches also ## "popular" user queries if more than one user happen to search for ## the same thing. Note that large numbers may lead to great memory ## consumption. We recommend a value not greater than 100. CFG_WEBSEARCH_SEARCH_CACHE_SIZE = 100 ## CFG_WEBSEARCH_FIELDS_CONVERT -- if you migrate from an older ## system, you may want to map field codes of your old system (such as ## 'ti') to Invenio/MySQL ("title"). Use Python dictionary syntax ## for the translation table, e.g. {'wau':'author', 'wti':'title'}. ## Usually you don't want to do that, and you would use empty dict {}. CFG_WEBSEARCH_FIELDS_CONVERT = {} ## CFG_WEBSEARCH_LIGHTSEARCH_PATTERN_BOX_WIDTH -- width of the ## search pattern window in the light search interface, in ## characters. CFG_WEBSEARCH_LIGHTSEARCH_PATTERN_BOX_WIDTH = 60 CFG_WEBSEARCH_LIGHTSEARCH_PATTERN_BOX_WIDTH = 60 ## CFG_WEBSEARCH_SIMPLESEARCH_PATTERN_BOX_WIDTH -- width of the search ## pattern window in the simple search interface, in characters. CFG_WEBSEARCH_SIMPLESEARCH_PATTERN_BOX_WIDTH = 40 ## CFG_WEBSEARCH_ADVANCEDSEARCH_PATTERN_BOX_WIDTH -- width of the ## search pattern window in the advanced search interface, in ## characters. CFG_WEBSEARCH_ADVANCEDSEARCH_PATTERN_BOX_WIDTH = 30 ## CFG_WEBSEARCH_NB_RECORDS_TO_SORT -- how many records do we still ## want to sort? For higher numbers we print only a warning and won't ## perform any sorting other than default 'latest records first', as ## sorting would be very time consuming then. We recommend a value of ## not more than a couple of thousands. CFG_WEBSEARCH_NB_RECORDS_TO_SORT = 1000 ## CFG_WEBSEARCH_CALL_BIBFORMAT -- if a record is being displayed but ## it was not preformatted in the "HTML brief" format, do we want to ## call BibFormatting on the fly? Put "1" for "yes" and "0" for "no". ## Note that "1" will display the record exactly as if it were fully ## preformatted, but it may be slow due to on-the-fly processing; "0" ## will display a default format very fast, but it may not have all ## the fields as in the fully preformatted HTML brief format. Note ## also that this option is active only for old (PHP) formats; the new ## (Python) formats are called on the fly by default anyway, since ## they are much faster. When usure, please set "0" here. CFG_WEBSEARCH_CALL_BIBFORMAT = 0 ## CFG_WEBSEARCH_USE_ALEPH_SYSNOS -- do we want to make old SYSNOs ## visible rather than MySQL's record IDs? You may use this if you ## migrate from a different e-doc system, and you store your old ## system numbers into 970__a. Put "1" for "yes" and "0" for ## "no". Usually you don't want to do that, though. CFG_WEBSEARCH_USE_ALEPH_SYSNOS = 0 ## CFG_WEBSEARCH_I18N_LATEST_ADDITIONS -- Put "1" if you want the ## "Latest Additions" in the web collection pages to show ## internationalized records. Useful only if your brief BibFormat ## templates contains internationalized strings. Otherwise put "0" in ## order not to slow down the creation of latest additions by WebColl. CFG_WEBSEARCH_I18N_LATEST_ADDITIONS = 0 ## CFG_WEBSEARCH_INSTANT_BROWSE -- the number of records to display ## under 'Latest Additions' in the web collection pages. CFG_WEBSEARCH_INSTANT_BROWSE = 10 ## CFG_WEBSEARCH_INSTANT_BROWSE_RSS -- the number of records to ## display in the RSS feed. CFG_WEBSEARCH_INSTANT_BROWSE_RSS = 25 ## CFG_WEBSEARCH_RSS_I18N_COLLECTIONS -- comma-separated list of ## collections that feature an internationalized RSS feed on their ## main seach interface page created by webcoll. Other collections ## will have RSS feed using CFG_SITE_LANG. CFG_WEBSEARCH_RSS_I18N_COLLECTIONS = ## CFG_WEBSEARCH_RSS_TTL -- number of minutes that indicates how long ## a feed cache is valid. CFG_WEBSEARCH_RSS_TTL = 360 ## CFG_WEBSEARCH_RSS_MAX_CACHED_REQUESTS -- maximum number of request kept ## in cache. If the cache is filled, following request are not cached. CFG_WEBSEARCH_RSS_MAX_CACHED_REQUESTS = 1000 ## CFG_WEBSEARCH_AUTHOR_ET_AL_THRESHOLD -- up to how many author names ## to print explicitely; for more print "et al". Note that this is ## used in default formatting that is seldomly used, as usually ## BibFormat defines all the format. The value below is only used ## when BibFormat fails, for example. CFG_WEBSEARCH_AUTHOR_ET_AL_THRESHOLD = 3 ## CFG_WEBSEARCH_NARROW_SEARCH_SHOW_GRANDSONS -- whether to show or ## not collection grandsons in Narrow Search boxes (sons are shown by ## default, grandsons are configurable here). Use 0 for no and 1 for ## yes. CFG_WEBSEARCH_NARROW_SEARCH_SHOW_GRANDSONS = 1 ## CFG_WEBSEARCH_CREATE_SIMILARLY_NAMED_AUTHORS_LINK_BOX -- shall we ## create help links for Ellis, Nick or Ellis, Nicholas and friends ## when Ellis, N was searched for? Useful if you have one author ## stored in the database under several name formats, namely surname ## comma firstname and surname comma initial cataloging policy. Use 0 ## for no and 1 for yes. CFG_WEBSEARCH_CREATE_SIMILARLY_NAMED_AUTHORS_LINK_BOX = 1 ## CFG_WEBSEARCH_USE_JSMATH_FOR_FORMATS -- jsMath is a JavaScript ## library that renders (La)TeX mathematical formulas in the client ## browser. This parameter must contain a comma-separated list of ## output formats for which to apply the jsMath rendering, for example ## "hb,hd". If the list is empty, jsMath is disabled. CFG_WEBSEARCH_USE_JSMATH_FOR_FORMATS = ## CFG_WEBSEARCH_EXTERNAL_COLLECTION_SEARCH_TIMEOUT -- when searching ## external collections (e.g. SPIRES, CiteSeer, etc), how many seconds ## do we wait for reply before abandonning? CFG_WEBSEARCH_EXTERNAL_COLLECTION_SEARCH_TIMEOUT = 5 ## CFG_WEBSEARCH_EXTERNAL_COLLECTION_SEARCH_MAXRESULTS -- how many ## results do we fetch? CFG_WEBSEARCH_EXTERNAL_COLLECTION_SEARCH_MAXRESULTS = 10 ## CFG_WEBSEARCH_SPLIT_BY_COLLECTION -- do we want to split the search ## results by collection or not? Use 0 for not, 1 for yes. CFG_WEBSEARCH_SPLIT_BY_COLLECTION = 1 ## CFG_WEBSEARCH_DEF_RECORDS_IN_GROUPS -- the default number of ## records to display per page in the search results pages. CFG_WEBSEARCH_DEF_RECORDS_IN_GROUPS = 10 ## CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS -- in order to limit denial of ## service attacks the total number of records per group displayed as a ## result of a search query will be limited to this number. Only the superuser ## queries will not be affected by this limit. CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS = 200 ## CFG_WEBSEARCH_PERMITTED_RESTRICTED_COLLECTIONS_LEVEL -- logged in users ## might have rights to access some restricted collections. This variable ## tweaks the kind of support the system will automatically provide to the ## user with respect to searching into these restricted collections. ## Set this to 0 in order to have the user to explicitly activate restricted ## collections in order to search into them. Set this to 1 in order to ## propose to the user the list of restricted collections to which he/she has ## rights (note: this is not yet implemented). Set this to 2 in order to ## silently add all the restricted collections to which the user has rights to ## to any query. ## Note: the system will discover which restricted collections a user has ## rights to, at login time. The time complexity of this procedure is ## proportional to the number of restricted collections. E.g. for a system ## with ~50 restricted collections, you might expect ~1s of delay in the ## login time, when this variable is set to a value higher than 0. CFG_WEBSEARCH_PERMITTED_RESTRICTED_COLLECTIONS_LEVEL = 0 ## CFG_WEBSEARCH_SHOW_COMMENT_COUNT -- do we want to show the 'N comments' ## links on the search engine pages? (useful only when you have allowed ## commenting) CFG_WEBSEARCH_SHOW_COMMENT_COUNT = 1 ## CFG_WEBSEARCH_SHOW_REVIEW_COUNT -- do we want to show the 'N reviews' ## links on the search engine pages? (useful only when you have allowed ## reviewing) CFG_WEBSEARCH_SHOW_REVIEW_COUNT = 1 ## CFG_WEBSEARCH_FULLTEXT_SNIPPETS -- how many full-text snippets to ## display for full-text searches? CFG_WEBSEARCH_FULLTEXT_SNIPPETS = 0 ## CFG_WEBSEARCH_FULLTEXT_SNIPPETS_WORDS -- how many context words ## to display around the pattern in the snippet? CFG_WEBSEARCH_FULLTEXT_SNIPPETS_WORDS = 4 ####################################### ## Part 4: BibHarvest OAI parameters ## ####################################### ## This part defines parameters for the Invenio OAI gateway. ## Useful if you are running Invenio as OAI data provider. ## CFG_OAI_ID_FIELD -- OAI identifier MARC field: CFG_OAI_ID_FIELD = 909COo ## CFG_OAI_SET_FIELD -- OAI set MARC field: CFG_OAI_SET_FIELD = 909COp ## CFG_OAI_DELETED_POLICY -- OAI deletedrecordspolicy ## (no/transient/persistent). CFG_OAI_DELETED_POLICY = no ## CFG_OAI_ID_PREFIX -- OAI identifier prefix: CFG_OAI_ID_PREFIX = atlantis.cern.ch ## CFG_OAI_SAMPLE_IDENTIFIER -- OAI sample identifier: CFG_OAI_SAMPLE_IDENTIFIER = oai:atlantis.cern.ch:CERN-TH-4036 ## CFG_OAI_IDENTIFY_DESCRIPTION -- description for the OAI Identify verb: CFG_OAI_IDENTIFY_DESCRIPTION = oai atlantis.cern.ch : oai:atlantis.cern.ch:CERN-TH-4036 http://atlantis.cern.ch/ Free and unlimited use by anybody with obligation to refer to original record Full content, i.e. preprints may not be harvested by robots Submission restricted. Submitted documents are subject of approval by OAI repository admins. ## CFG_OAI_LOAD -- OAI number of records in a response: CFG_OAI_LOAD = 1000 ## CFG_OAI_EXPIRE -- OAI resumptionToken expiration time: CFG_OAI_EXPIRE = 90000 ## CFG_OAI_SLEEP -- service unavailable between two consecutive ## requests for CFG_OAI_SLEEP seconds: CFG_OAI_SLEEP = 10 ################################## ## Part 5: WebSubmit parameters ## ################################## ## This section contains some configuration parameters for WebSubmit ## module. Please note that WebSubmit is mostly configured on ## run-time via its WebSubmit Admin web interface. The parameters ## below are the ones that you do not probably want to modify during ## the runtime. ## CFG_WEBSUBMIT_FILESYSTEM_BIBDOC_GROUP_LIMIT -- the fulltext ## documents are stored under "/opt/invenio/var/data/files/gX/Y" ## directories where X is 0,1,... and Y stands for bibdoc ID. Thusly ## documents Y are grouped into directories X and this variable ## indicates the maximum number of documents Y stored in each ## directory X. This limit is imposed solely for filesystem ## performance reasons in order not to have too many subdirectories in ## a given directory. CFG_WEBSUBMIT_FILESYSTEM_BIBDOC_GROUP_LIMIT = 5000 ## CFG_WEBSUBMIT_ADDITIONAL_KNOWN_FILE_EXTENSIONS -- a comma-separated ## list of document extensions not listed in Python standard mimetype ## library that should be recognized by Invenio. CFG_WEBSUBMIT_ADDITIONAL_KNOWN_FILE_EXTENSIONS = hpg,link,lis,llb,mat,mpp,msg,docx,docm,xlsx,xlsm,xlsb,pptx,pptm,ppsx,ppsm ## CFG_BIBDOCFILE_USE_XSENDFILE -- if your web server supports ## XSendfile header, you may want to enable this feature in order for ## to Invenio tell the web server to stream files for download (after ## proper authorization checks) by web server's means. This helps to ## liberate Invenio worker processes from being busy with sending big ## files to clients. The web server will take care of that. Note: ## this feature is still somewhat experimental. Note: when enabled ## (set to 1), then you have to also regenerate Apache vhost conf ## snippets (inveniocfg --update-config-py --create-apache-conf). CFG_BIBDOCFILE_USE_XSENDFILE = 0 ## CFG_BIBDOCFILE_MD5_CHECK_PROBABILITY -- a number between 0 and ## 1 that indicates probability with which MD5 checksum will be ## verified when streaming bibdocfile-managed files. (0.1 will cause ## the check to be performed once for every 10 downloads) CFG_BIBDOCFILE_MD5_CHECK_PROBABILITY = 0.1 ## CFG_OPENOFFICE_SERVER_HOST -- the host where an OpenOffice Server is ## listening to. If localhost an OpenOffice server will be started ## automatically if it is not already running. ## Note: if you set this to an empty value this will disable the usage of ## OpenOffice for converting documents. ## If you set this to something different than localhost you'll have to take ## care to have an OpenOffice server running on the corresponding host and ## to install the same OpenOffice release both on the client and on the server ## side. ## In order to launch an OpenOffice server on a remote machine, just start ## the usual 'soffice' executable in this way: ## $> soffice -headless -nologo -nodefault -norestore -nofirststartwizard \ ## .. -accept=socket,host=HOST,port=PORT;urp;StarOffice.ComponentContext CFG_OPENOFFICE_SERVER_HOST = localhost ## CFG_OPENOFFICE_SERVER_PORT -- the port where an OpenOffice Server is ## listening to. CFG_OPENOFFICE_SERVER_PORT = 2002 ## CFG_OPENOFFICE_USER -- the user that will be used to launch the OpenOffice ## client. It is recommended to set this to a user who don't own files, like ## e.g. 'nobody'. You should also authorize your Apache server user to be ## able to become this user, e.g. by adding to your /etc/sudoers the following ## line: ## "apache ALL=(nobody) NOPASSWD: ALL" ## provided that apache is the username corresponding to the Apache user. ## On some machine this might be apache2 or www-data. CFG_OPENOFFICE_USER = nobody ################################# ## Part 6: BibIndex parameters ## ################################# ## This section contains some configuration parameters for BibIndex ## module. Please note that BibIndex is mostly configured on run-time ## via its BibIndex Admin web interface. The parameters below are the ## ones that you do not probably want to modify very often during the ## runtime. ## CFG_BIBINDEX_FULLTEXT_INDEX_LOCAL_FILES_ONLY -- when fulltext indexing, do ## you want to index locally stored files only, or also external URLs? ## Use "0" to say "no" and "1" to say "yes". CFG_BIBINDEX_FULLTEXT_INDEX_LOCAL_FILES_ONLY = 0 ## CFG_BIBINDEX_REMOVE_STOPWORDS -- when indexing, do we want to remove ## stopwords? Use "0" to say "no" and "1" to say "yes". CFG_BIBINDEX_REMOVE_STOPWORDS = 0 ## CFG_BIBINDEX_CHARS_ALPHANUMERIC_SEPARATORS -- characters considered as ## alphanumeric separators of word-blocks inside words. You probably ## don't want to change this. CFG_BIBINDEX_CHARS_ALPHANUMERIC_SEPARATORS = \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~ ## CFG_BIBINDEX_CHARS_PUNCTUATION -- characters considered as punctuation ## between word-blocks inside words. You probably don't want to ## change this. CFG_BIBINDEX_CHARS_PUNCTUATION = \.\,\:\;\?\!\" ## CFG_BIBINDEX_REMOVE_HTML_MARKUP -- should we attempt to remove HTML markup ## before indexing? Use 1 if you have HTML markup inside metadata ## (e.g. in abstracts), use 0 otherwise. CFG_BIBINDEX_REMOVE_HTML_MARKUP = 0 ## CFG_BIBINDEX_REMOVE_LATEX_MARKUP -- should we attempt to remove LATEX markup ## before indexing? Use 1 if you have LATEX markup inside metadata ## (e.g. in abstracts), use 0 otherwise. CFG_BIBINDEX_REMOVE_LATEX_MARKUP = 0 ## CFG_BIBINDEX_MIN_WORD_LENGTH -- minimum word length allowed to be added to ## index. The terms smaller then this amount will be discarded. ## Useful to keep the database clean, however you can safely leave ## this value on 0 for up to 1,000,000 documents. CFG_BIBINDEX_MIN_WORD_LENGTH = 0 ## CFG_BIBINDEX_URLOPENER_USERNAME and CFG_BIBINDEX_URLOPENER_PASSWORD -- ## access credentials to access restricted URLs, interesting only if ## you are fulltext-indexing files located on a remote server that is ## only available via username/password. But it's probably better to ## handle this case via IP or some convention; the current scheme is ## mostly there for demo only. CFG_BIBINDEX_URLOPENER_USERNAME = mysuperuser CFG_BIBINDEX_URLOPENER_PASSWORD = mysuperpass ## CFG_INTBITSET_ENABLE_SANITY_CHECKS -- ## Enable sanity checks for integers passed to the intbitset data ## structures. It is good to enable this during debugging ## and to disable this value for speed improvements. CFG_INTBITSET_ENABLE_SANITY_CHECKS = False ## CFG_BIBINDEX_PERFORM_OCR_ON_DOCNAMES -- regular expression that matches ## docnames for which OCR is desired (set this to .* in order to enable ## OCR in general, set this to empty in order to disable it.) CFG_BIBINDEX_PERFORM_OCR_ON_DOCNAMES = scan-.* ## CFG_BIBINDEX_SPLASH_PAGES -- regular expression that matches URLs ## that are not to be indexed but that indirectly refers to documents ## that are supposed to be indexed. CFG_BIBINDEX_SPLASH_PAGES = http://documents\.cern\.ch/setlink\?.* ## CFG_BIBINDEX_AUTHOR_WORD_INDEX_EXCLUDE_FIRST_NAMES -- do we want ## the author word index to exclude first names to keep only last ## names? If set to True, then for the author `Bernard, Denis', only ## `Bernard' will be indexed in the word index, not `Denis'. Note ## that if you change this variable, you have to re-index the author ## index via `bibindex -w author -R'. CFG_BIBINDEX_AUTHOR_WORD_INDEX_EXCLUDE_FIRST_NAMES = False ####################################### ## Part 7: Access control parameters ## ####################################### ## This section contains some configuration parameters for the access ## control system. Please note that WebAccess is mostly configured on ## run-time via its WebAccess Admin web interface. The parameters ## below are the ones that you do not probably want to modify very ## often during the runtime. (If you do want to modify them during ## runtime, for example te deny access temporarily because of backups, ## you can edit access_control_config.py directly, no need to get back ## here and no need to redo the make process.) ## CFG_ACCESS_CONTROL_LEVEL_SITE -- defines how open this site is. ## Use 0 for normal operation of the site, 1 for read-only site (all ## write operations temporarily closed), 2 for site fully closed, ## 3 for also disabling any database connection. ## Useful for site maintenance. CFG_ACCESS_CONTROL_LEVEL_SITE = 0 ## CFG_ACCESS_CONTROL_LEVEL_GUESTS -- guest users access policy. Use ## 0 to allow guest users, 1 not to allow them (all users must login). CFG_ACCESS_CONTROL_LEVEL_GUESTS = 0 ## CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS -- account registration and ## activation policy. When 0, users can register and accounts are ## automatically activated. When 1, users can register but admin must ## activate the accounts. When 2, users cannot register nor update ## their email address, only admin can register accounts. When 3, ## users cannot register nor update email address nor password, only ## admin can register accounts. When 4, the same as 3 applies, nor ## user cannot change his login method. When 5, then the same as 4 ## applies, plus info about how to get an account is hidden from the ## login page. CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS = 0 ## CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN -- limit account ## registration to certain email addresses? If wanted, give domain ## name below, e.g. "cern.ch". If not wanted, leave it empty. CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN = ## CFG_ACCESS_CONTROL_NOTIFY_ADMIN_ABOUT_NEW_ACCOUNTS -- send a ## notification email to the administrator when a new account is ## created? Use 0 for no, 1 for yes. CFG_ACCESS_CONTROL_NOTIFY_ADMIN_ABOUT_NEW_ACCOUNTS = 0 ## CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT -- send a ## notification email to the user when a new account is created in order to ## to verify the validity of the provided email address? Use ## 0 for no, 1 for yes. CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT = 1 ## CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_ACTIVATION -- send a ## notification email to the user when a new account is activated? ## Use 0 for no, 1 for yes. CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_ACTIVATION = 0 ## CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_DELETION -- send a ## notification email to the user when a new account is deleted or ## account demand rejected? Use 0 for no, 1 for yes. CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_DELETION = 0 ## CFG_APACHE_PASSWORD_FILE -- the file where Apache user credentials ## are stored. Must be an absolute pathname. If the value does not ## start by a slash, it is considered to be the filename of a file ## located under prefix/var/tmp directory. This is useful for the ## demo site testing purposes. For the production site, if you plan ## to restrict access to some collections based on the Apache user ## authentication mechanism, you should put here an absolute path to ## your Apache password file. CFG_APACHE_PASSWORD_FILE = demo-site-apache-user-passwords ## CFG_APACHE_GROUP_FILE -- the file where Apache user groups are ## defined. See the documentation of the preceding config variable. CFG_APACHE_GROUP_FILE = demo-site-apache-user-groups ################################### ## Part 8: WebSession parameters ## ################################### ## This section contains some configuration parameters for tweaking ## session handling. ## CFG_WEBSESSION_EXPIRY_LIMIT_DEFAULT -- number of days after which a session ## and the corresponding cookie is considered expired. CFG_WEBSESSION_EXPIRY_LIMIT_DEFAULT = 2 ## CFG_WEBSESSION_EXPIRY_LIMIT_REMEMBER -- number of days after which a session ## and the corresponding cookie is considered expired, when the user has ## requested to permanently stay logged in. CFG_WEBSESSION_EXPIRY_LIMIT_REMEMBER = 365 ## CFG_WEBSESSION_RESET_PASSWORD_EXPIRE_IN_DAYS -- when user requested ## a password reset, for how many days is the URL valid? CFG_WEBSESSION_RESET_PASSWORD_EXPIRE_IN_DAYS = 3 ## CFG_WEBSESSION_ADDRESS_ACTIVATION_EXPIRE_IN_DAYS -- when an account ## activation email was sent, for how many days is the URL valid? CFG_WEBSESSION_ADDRESS_ACTIVATION_EXPIRE_IN_DAYS = 3 ## CFG_WEBSESSION_NOT_CONFIRMED_EMAIL_ADDRESS_EXPIRE_IN_DAYS -- when ## user won't confirm his email address and not complete ## registeration, after how many days will it expire? CFG_WEBSESSION_NOT_CONFIRMED_EMAIL_ADDRESS_EXPIRE_IN_DAYS = 10 ## CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS -- when set to 1, the session ## system allocates the same uid=0 to all guests users regardless of where they ## come from. 0 allocate a unique uid to each guest. CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS = 0 ################################ ## Part 9: BibRank parameters ## ################################ ## This section contains some configuration parameters for the ranking ## system. ## CFG_BIBRANK_SHOW_READING_STATS -- do we want to show reading ## similarity stats? ('People who viewed this page also viewed') CFG_BIBRANK_SHOW_READING_STATS = 1 ## CFG_BIBRANK_SHOW_DOWNLOAD_STATS -- do we want to show the download ## similarity stats? ('People who downloaded this document also ## downloaded') CFG_BIBRANK_SHOW_DOWNLOAD_STATS = 1 ## CFG_BIBRANK_SHOW_DOWNLOAD_GRAPHS -- do we want to show download ## history graph? CFG_BIBRANK_SHOW_DOWNLOAD_GRAPHS = 1 ## CFG_BIBRANK_SHOW_DOWNLOAD_GRAPHS_CLIENT_IP_DISTRIBUTION -- do we ## want to show a graph representing the distribution of client IPs ## downloading given document? CFG_BIBRANK_SHOW_DOWNLOAD_GRAPHS_CLIENT_IP_DISTRIBUTION = 0 ## CFG_BIBRANK_SHOW_CITATION_LINKS -- do we want to show the 'Cited ## by' links? (useful only when you have citations in the metadata) CFG_BIBRANK_SHOW_CITATION_LINKS = 1 ## CFG_BIBRANK_SHOW_CITATION_STATS -- de we want to show citation ## stats? ('Cited by M recors', 'Co-cited with N records') CFG_BIBRANK_SHOW_CITATION_STATS = 1 ## CFG_BIBRANK_SHOW_CITATION_GRAPHS -- do we want to show citation ## history graph? CFG_BIBRANK_SHOW_CITATION_GRAPHS = 1 #################################### ## Part 10: WebComment parameters ## #################################### ## This section contains some configuration parameters for the ## commenting and reviewing facilities. ## CFG_WEBCOMMENT_ALLOW_COMMENTS -- do we want to allow users write ## public comments on records? CFG_WEBCOMMENT_ALLOW_COMMENTS = 1 ## CFG_WEBCOMMENT_ALLOW_REVIEWS -- do we want to allow users write ## public reviews of records? CFG_WEBCOMMENT_ALLOW_REVIEWS = 1 ## CFG_WEBCOMMENT_ALLOW_SHORT_REVIEWS -- do we want to allow short ## reviews, that is just the attribution of stars without submitting ## detailed review text? CFG_WEBCOMMENT_ALLOW_SHORT_REVIEWS = 0 ## CFG_WEBCOMMENT_NB_REPORTS_BEFORE_SEND_EMAIL_TO_ADMIN -- if users ## report a comment to be abusive, how many they have to be before the ## site admin is alerted? CFG_WEBCOMMENT_NB_REPORTS_BEFORE_SEND_EMAIL_TO_ADMIN = 5 ## CFG_WEBCOMMENT_NB_COMMENTS_IN_DETAILED_VIEW -- how many comments do ## we display in the detailed record page upon welcome? CFG_WEBCOMMENT_NB_COMMENTS_IN_DETAILED_VIEW = 1 ## CFG_WEBCOMMENT_NB_REVIEWS_IN_DETAILED_VIEW -- how many reviews do ## we display in the detailed record page upon welcome? CFG_WEBCOMMENT_NB_REVIEWS_IN_DETAILED_VIEW = 1 ## CFG_WEBCOMMENT_ADMIN_NOTIFICATION_LEVEL -- do we notify the site ## admin after every comment? CFG_WEBCOMMENT_ADMIN_NOTIFICATION_LEVEL = 1 ## CFG_WEBCOMMENT_TIMELIMIT_PROCESSING_COMMENTS_IN_SECONDS -- how many ## elapsed seconds do we consider enough when checking for possible ## multiple comment submissions by a user? CFG_WEBCOMMENT_TIMELIMIT_PROCESSING_COMMENTS_IN_SECONDS = 20 ## CFG_WEBCOMMENT_TIMELIMIT_PROCESSING_REVIEWS_IN_SECONDS -- how many ## elapsed seconds do we consider enough when checking for possible ## multiple review submissions by a user? CFG_WEBCOMMENT_TIMELIMIT_PROCESSING_REVIEWS_IN_SECONDS = 20 ## CFG_WEBCOMMENT_USE_RICH_EDITOR -- enable the WYSIWYG ## Javascript-based editor when user edits comments? CFG_WEBCOMMENT_USE_RICH_TEXT_EDITOR = False ## CFG_WEBCOMMENT_ALERT_ENGINE_EMAIL -- the email address from which the ## alert emails will appear to be sent: CFG_WEBCOMMENT_ALERT_ENGINE_EMAIL = info@invenio-software.org ## CFG_WEBCOMMENT_DEFAULT_MODERATOR -- if no rules are ## specified to indicate who is the comment moderator of ## a collection, this person will be used as default CFG_WEBCOMMENT_DEFAULT_MODERATOR = info@invenio-software.org ## CFG_WEBCOMMENT_USE_JSMATH_IN_COMMENTS -- do we want to allow the use ## of jsmath plugin to render latex input in comments? CFG_WEBCOMMENT_USE_JSMATH_IN_COMMENTS = 1 ## CFG_WEBCOMMENT_AUTHOR_DELETE_COMMENT_OPTION -- allow comment author to ## delete its own comment? CFG_WEBCOMMENT_AUTHOR_DELETE_COMMENT_OPTION = 1 ################################## ## Part 11: BibSched parameters ## ################################## ## This section contains some configuration parameters for the ## bibliographic task scheduler. ## CFG_BIBSCHED_REFRESHTIME -- how often do we want to refresh ## bibsched monitor? (in seconds) CFG_BIBSCHED_REFRESHTIME = 5 ## CFG_BIBSCHED_LOG_PAGER -- what pager to use to view bibsched task ## logs? CFG_BIBSCHED_LOG_PAGER = /bin/more ## CFG_BIBSCHED_GC_TASKS_OLDER_THAN -- after how many days to perform the ## gargbage collector of BibSched queue (i.e. removing/moving task to archive). CFG_BIBSCHED_GC_TASKS_OLDER_THAN = 30 ## CFG_BIBSCHED_GC_TASKS_TO_REMOVE -- list of BibTask that can be safely ## removed from the BibSched queue once they are DONE. CFG_BIBSCHED_GC_TASKS_TO_REMOVE = bibindex,bibreformat,webcoll,bibrank,inveniogc ## CFG_BIBSCHED_GC_TASKS_TO_ARCHIVE -- list of BibTasks that should be safely ## archived out of the BibSched queue once they are DONE. CFG_BIBSCHED_GC_TASKS_TO_ARCHIVE = bibupload,oaiarchive ## CFG_BIBSCHED_MAX_NUMBER_CONCURRENT_TASKS -- maximum number of BibTasks ## that can run concurrently. ## NOTE: concurrent tasks are still considered as an experimental ## feature. Please keep this value set to 1 on production environments. CFG_BIBSCHED_MAX_NUMBER_CONCURRENT_TASKS = 1 ## CFG_BIBSCHED_PROCESS_USER -- bibsched and bibtask processes must ## usually run under the same identity as the Apache web server ## process in order to share proper file read/write privileges. If ## you want to force some other bibsched/bibtask user, e.g. because ## you are using a local `invenio' user that belongs to your ## `www-data' Apache user group and so shares writing rights with your ## Apache web server process in this way, then please set its username ## identity here. Otherwise we shall check whether your ## bibsched/bibtask processes are run under the same identity as your ## Apache web server process (in which case you can leave the default ## empty value here). CFG_BIBSCHED_PROCESS_USER = ################################### ## Part 12: WebBasket parameters ## ################################### ## CFG_WEBBASKET_MAX_NUMBER_OF_DISPLAYED_BASKETS -- a safety limit for ## a maximum number of displayed baskets CFG_WEBBASKET_MAX_NUMBER_OF_DISPLAYED_BASKETS = 20 ## CFG_WEBBASKET_USE_RICH_TEXT_EDITOR -- enable the WYSIWYG ## Javascript-based editor when user edits comments in WebBasket? CFG_WEBBASKET_USE_RICH_TEXT_EDITOR = False ################################## ## Part 13: WebAlert parameters ## ################################## ## This section contains some configuration parameters for the ## automatic email notification alert system. ## CFG_WEBALERT_ALERT_ENGINE_EMAIL -- the email address from which the ## alert emails will appear to be sent: CFG_WEBALERT_ALERT_ENGINE_EMAIL = info@invenio-software.org ## CFG_WEBALERT_MAX_NUM_OF_RECORDS_IN_ALERT_EMAIL -- how many records ## at most do we send in an outgoing alert email? CFG_WEBALERT_MAX_NUM_OF_RECORDS_IN_ALERT_EMAIL = 20 ## CFG_WEBALERT_MAX_NUM_OF_CHARS_PER_LINE_IN_ALERT_EMAIL -- number of ## chars per line in an outgoing alert email? CFG_WEBALERT_MAX_NUM_OF_CHARS_PER_LINE_IN_ALERT_EMAIL = 72 ## CFG_WEBALERT_SEND_EMAIL_NUMBER_OF_TRIES -- when sending alert ## emails fails, how many times we retry? CFG_WEBALERT_SEND_EMAIL_NUMBER_OF_TRIES = 3 ## CFG_WEBALERT_SEND_EMAIL_SLEEPTIME_BETWEEN_TRIES -- when sending ## alert emails fails, what is the sleeptime between tries? (in ## seconds) CFG_WEBALERT_SEND_EMAIL_SLEEPTIME_BETWEEN_TRIES = 300 #################################### ## Part 14: WebMessage parameters ## #################################### ## CFG_WEBMESSAGE_MAX_SIZE_OF_MESSAGE -- how large web messages do we ## allow? CFG_WEBMESSAGE_MAX_SIZE_OF_MESSAGE = 20000 ## CFG_WEBMESSAGE_MAX_NB_OF_MESSAGES -- how many messages for a ## regular user do we allow in its inbox? CFG_WEBMESSAGE_MAX_NB_OF_MESSAGES = 30 ## CFG_WEBMESSAGE_DAYS_BEFORE_DELETE_ORPHANS -- how many days before ## we delete orphaned messages? CFG_WEBMESSAGE_DAYS_BEFORE_DELETE_ORPHANS = 60 ################################## ## Part 15: MiscUtil parameters ## ################################## ## CFG_MISCUTIL_SQL_USE_SQLALCHEMY -- whether to use SQLAlchemy.pool ## in the DB engine of Invenio. It is okay to enable this flag ## even if you have not installed SQLAlchemy. Note that Invenio will ## loose some perfomance if this option is enabled. CFG_MISCUTIL_SQL_USE_SQLALCHEMY = False ## CFG_MISCUTIL_SQL_RUN_SQL_MANY_LIMIT -- how many queries can we run ## inside run_sql_many() in one SQL statement? The limit value ## depends on MySQL's max_allowed_packet configuration. CFG_MISCUTIL_SQL_RUN_SQL_MANY_LIMIT = 10000 ## CFG_MISCUTIL_SMTP_HOST -- which server to use as outgoing mail server to ## send outgoing emails generated by the system, for example concerning ## submissions or email notification alerts. CFG_MISCUTIL_SMTP_HOST = localhost ## CFG_MISCUTIL_SMTP_PORT -- which port to use on the outgoing mail server ## defined in the previous step. CFG_MISCUTIL_SMTP_PORT = 25 ## CFG_MISCUTILS_DEFAULT_PROCESS_TIMEOUT -- the default number of seconds after ## which a process launched trough shellutils.run_process_with_timeout will ## be killed. This is useful to catch runaway processes. CFG_MISCUTIL_DEFAULT_PROCESS_TIMEOUT = 300 ## CFG_PLOTEXTRACTOR_ARXIV_BASE -- for acquiring source tarballs for plot ## extraction, where should we look? If nothing is set, we'll just go ## to arXiv, but this can be a filesystem location, too CFG_PLOTEXTRACTOR_ARXIV_BASE = ################################# ## Part 16: BibEdit parameters ## ################################# ## CFG_BIBEDIT_TIMEOUT -- when a user edits a record, this record is ## locked to prevent other users to edit it at the same time. ## How many seconds of inactivity before the locked record again will be free ## for other people to edit? CFG_BIBEDIT_TIMEOUT = 3600 ## CFG_BIBEDIT_LOCKLEVEL -- when a user tries to edit a record which there ## is a pending bibupload task for in the queue, this shouldn't be permitted. ## The lock level determines how thouroughly the queue should be investigated ## to determine if this is the case. ## Level 0 - always permits editing, doesn't look at the queue ## (unsafe, use only if you know what you are doing) ## Level 1 - permits editing if there are no queued bibedit tasks for this record ## (safe with respect to bibedit, but not for other bibupload maintenance jobs) ## Level 2 - permits editing if there are no queued bibupload tasks of any sort ## (safe, but may lock more than necessary if many cataloguers around) ## Level 3 - permits editing if no queued bibupload task concerns given record ## (safe, most precise locking, but slow, ## checks for 001/EXTERNAL_SYSNO_TAG/EXTERNAL_OAIID_TAG) ## The recommended level is 3 (default) or 2 (if you use maintenance jobs often). CFG_BIBEDIT_LOCKLEVEL = 3 ## CFG_BIBEDIT_PROTECTED_FIELDS -- a comma-separated list of fields that BibEdit ## will not allow to be added, edited or deleted. Wildcards are not supported, ## but conceptually a wildcard is added at the end of every field specification. ## Examples: ## 500A - protect all MARC fields with tag 500 and first indicator A ## 5 - protect all MARC fields in the 500-series. ## 909C_a - protect subfield a in tag 909 with first indicator C and empty ## second indicator ## Note that 001 is protected by default, but if protection of other ## identifiers or automated fields is a requirement, they should be added to ## this list. CFG_BIBEDIT_PROTECTED_FIELDS = ## CFG_BIBEDITMULTI_LIMIT_INSTANT_PROCESSING -- maximum number of records ## that can be modified instantly using the multi-record editor. Above ## this limit, modifications will only be executed in limited hours. CFG_BIBEDITMULTI_LIMIT_INSTANT_PROCESSING = 2000 ## CFG_BIBEDITMULTI_LIMIT_DELAYED_PROCESSING -- maximum number of records ## that can be send for modification without having a superadmin role. ## If the number of records is between CFG_BIBEDITMULTI_LIMIT_INSTANT_PROCESSING ## and this number, the modifications will take place only in limited hours. CFG_BIBEDITMULTI_LIMIT_DELAYED_PROCESSING = 20000 ## CFG_BIBEDITMULTI_LIMIT_DELAYED_PROCESSING_TIME -- Allowed time to ## execute modifications on records, when the number exceeds ## CFG_BIBEDITMULTI_LIMIT_INSTANT_PROCESSING. CFG_BIBEDITMULTI_LIMIT_DELAYED_PROCESSING_TIME = 22:00-05:00 ################################### ## Part 17: BibUpload parameters ## ################################### ## CFG_BIBUPLOAD_REFERENCE_TAG -- where do we store references? CFG_BIBUPLOAD_REFERENCE_TAG = 999 ## CFG_BIBUPLOAD_EXTERNAL_SYSNO_TAG -- where do we store external ## system numbers? Useful for matching when our records come from an ## external digital library system. CFG_BIBUPLOAD_EXTERNAL_SYSNO_TAG = 970__a ## CFG_BIBUPLOAD_EXTERNAL_OAIID_TAG -- where do we store OAI ID tags ## of harvested records? Useful for matching when we harvest stuff ## via OAI that we do not want to reexport via Invenio OAI; so records ## may have only the source OAI ID stored in this tag (kind of like ## external system number too). CFG_BIBUPLOAD_EXTERNAL_OAIID_TAG = 035__a ## CFG_BIBUPLOAD_EXTERNAL_OAIID_PROVENANCE_TAG -- where do we store OAI SRC ## tags of harvested records? Useful for matching when we harvest stuff ## via OAI that we do not want to reexport via Invenio OAI; so records ## may have only the source OAI SRC stored in this tag (kind of like ## external system number too). Note that the field should be the same of ## CFG_BIBUPLOAD_EXTERNAL_OAIID_TAG. CFG_BIBUPLOAD_EXTERNAL_OAIID_PROVENANCE_TAG = 035__9 ## CFG_BIBUPLOAD_STRONG_TAGS -- a comma-separated list of tags that ## are strong enough to resist the replace mode. Useful for tags that ## might be created from an external non-metadata-like source, ## e.g. the information about the number of copies left. CFG_BIBUPLOAD_STRONG_TAGS = 964 ## CFG_BIBUPLOAD_CONTROLLED_PROVENANCE_TAGS -- a comma-separated list ## of tags that contain provenance information that should be checked ## in the bibupload correct mode via matching provenance codes. (Only ## field instances of the same provenance information would be acted ## upon.) Please specify the whole tag info up to subfield codes. CFG_BIBUPLOAD_CONTROLLED_PROVENANCE_TAGS = 6531_9 ## CFG_BIBUPLOAD_FFT_ALLOWED_LOCAL_PATHS -- a comma-separated list of system ## paths from which it is allowed to take fulltextes that will be uploaded via ## FFT (CFG_TMPDIR is included by default). CFG_BIBUPLOAD_FFT_ALLOWED_LOCAL_PATHS = /tmp,/home ## CFG_BIBUPLOAD_SERIALIZE_RECORD_STRUCTURE -- do we want to serialize ## internal representation of records (Pythonic record structure) into ## the database? This can improve internal processing speed of some ## operations at the price of somewhat bigger disk space usage. ## If you change this value after some records have already been added ## to your installation, you may want to run: ## $ /opt/invenio/bin/inveniocfg --reset-recstruct-cache ## in order to either erase the cache thus freeing database space, ## or to fill the cache for all records that have not been cached yet. CFG_BIBUPLOAD_SERIALIZE_RECORD_STRUCTURE = 1 ## CFG_BATCHUPLOADER_FILENAME_MATCHING_POLICY -- a comma-separated list ## indicating which fields match the file names of the documents to be ## uploaded. ## The matching will be done in the same order as the list provided. CFG_BATCHUPLOADER_FILENAME_MATCHING_POLICY = reportnumber,recid ## CFG_BATCHUPLOADER_DAEMON_DIR -- Directory where the batchuploader daemon ## will look for the subfolders metadata and document by default. ## If path is relative, CFG_PREFIX will be joined as a prefix CFG_BATCHUPLOADER_DAEMON_DIR = var/batchupload ## CFG_BATCHUPLOADER_WEB_ROBOT_AGENT -- Comma-separated list to specify the ## agents permitted when calling batch uploader web interface ## cdsweb.cern.ch/batchuploader/robotupload ## if using a curl, eg: curl xxx -A invenio_webupload CFG_BATCHUPLOADER_WEB_ROBOT_AGENT = invenio_webupload ## CFG_BATCHUPLOADER_WEB_ROBOT_RIGHTS -- Access list specifying for each ## IP address, which collections are allowed using batch uploader robot ## interface. CFG_BATCHUPLOADER_WEB_ROBOT_RIGHTS = { '10.0.0.1': ['BOOK', 'REPORT'], # Example 1 '10.0.0.2': ['POETRY', 'PREPRINT'], # Example 2 } #################################### ## Part 18: BibCatalog parameters ## #################################### ## EXPERIMENTAL: Please do not use. CFG_BIBCATALOG_SYSTEM = CFG_BIBCATALOG_SYSTEM_RT_CLI = /usr/bin/rt CFG_BIBCATALOG_SYSTEM_RT_URL = http://localhost/rt3 CFG_BIBCATALOG_QUEUES = General #################################### ## Part 19: BibFormat parameters ## #################################### ## CFG_BIBFORMAT_HIDDEN_TAGS -- comma-separated list of MARC tags that ## are not shown to users not having cataloging authorizations. CFG_BIBFORMAT_HIDDEN_TAGS = 595 ########################## ## THAT's ALL, FOLKS! ## ########################## diff --git a/modules/bibcirculation/lib/bibcirculation_webinterface.py b/modules/bibcirculation/lib/bibcirculation_webinterface.py index 8084c6ad6..7252a47ca 100644 --- a/modules/bibcirculation/lib/bibcirculation_webinterface.py +++ b/modules/bibcirculation/lib/bibcirculation_webinterface.py @@ -1,747 +1,747 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ Bibcirculation web interface """ __revision__ = "$Id$" __lastupdated__ = """$Date$""" # others invenio imports from invenio.config import CFG_SITE_LANG, \ CFG_SITE_URL, \ CFG_SITE_SECURE_URL, \ CFG_ACCESS_CONTROL_LEVEL_SITE, \ CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS from invenio.webuser import getUid, page_not_authorized, isGuestUser, \ collect_user_info from invenio.webpage import page, pageheaderonly, pagefooteronly from invenio.search_engine import create_navtrail_links, \ guess_primary_collection_of_a_record, \ get_colID, check_user_can_view_record, \ record_exists from invenio.urlutils import redirect_to_url, \ make_canonical_urlargd from invenio.messages import gettext_set_language from invenio.webinterface_handler import wash_urlargd, WebInterfaceDirectory from invenio.websearchadminlib import get_detailed_page_tabs from invenio.access_control_config import VIEWRESTRCOLL from invenio.access_control_mailcookie import mail_cookie_create_authorize_action import invenio.template webstyle_templates = invenio.template.load('webstyle') websearch_templates = invenio.template.load('websearch') # bibcirculation imports bibcirculation_templates = invenio.template.load('bibcirculation') from invenio.bibcirculation import perform_new_request, \ perform_new_request_send, \ perform_get_holdings_information, \ perform_borrower_loans, \ perform_loanshistoricaloverview, \ display_ill_form, \ ill_register_request, \ ill_request_with_recid, \ ill_register_request_with_recid class WebInterfaceYourLoansPages(WebInterfaceDirectory): """Defines the set of /yourloans pages.""" _exports = ['', 'display', 'loanshistoricaloverview'] def index(self, req, form): """ The function called by default """ redirect_to_url(req, "%s/yourloans/display?%s" % (CFG_SITE_URL, req.args)) def display(self, req, form): """ Displays all loans of a given user @param ln: language @return the page for inbox """ argd = wash_urlargd(form, {'barcode': (str, ""), 'borrower_id': (int, 0), 'request_id': (int, 0)}) # Check if user is logged uid = getUid(req) if CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "%s/yourloans/display" % \ (CFG_SITE_URL,), navmenuid="yourloans") elif uid == -1 or isGuestUser(uid): return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourloans/display%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {})), norobot=True) _ = gettext_set_language(argd['ln']) user_info = collect_user_info(req) if not user_info['precached_useloans']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use loans.")) body = perform_borrower_loans(uid=uid, barcode=argd['barcode'], borrower_id=argd['borrower_id'], request_id=argd['request_id'], ln=argd['ln']) return page(title = _("Your Loans"), body = body, uid = uid, lastupdated = __lastupdated__, req = req, language = argd['ln'], navmenuid = "yourloans") def loanshistoricaloverview(self, req, form): """ Show loans historical overview. """ argd = wash_urlargd(form, {}) # Check if user is logged uid = getUid(req) if CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "%s/yourloans/loanshistoricaloverview" % \ (CFG_SITE_URL,), navmenuid="yourloans") elif uid == -1 or isGuestUser(uid): return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourloans/loanshistoricaloverview%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {})), norobot=True) _ = gettext_set_language(argd['ln']) user_info = collect_user_info(req) if not user_info['precached_useloans']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use loans.")) body = perform_loanshistoricaloverview(uid=uid, ln=argd['ln']) return page(title = _("Loans - historical overview"), body = body, uid = uid, lastupdated = __lastupdated__, req = req, language = argd['ln'], navmenuid = "yourloans") class WebInterfaceILLPages(WebInterfaceDirectory): """Defines the set of /ill pages.""" _exports = ['', 'display', 'register_request'] def index(self, req, form): """ The function called by default """ redirect_to_url(req, "%s/ill/display?%s" % (CFG_SITE_URL, req.args)) def display(self, req, form): """ Displays all loans of a given user @param ln: language @return the page for inbox """ argd = wash_urlargd(form, {}) # Check if user is logged uid = getUid(req) if CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "%s/ill/display" % \ (CFG_SITE_URL,), navmenuid="ill") elif uid == -1 or isGuestUser(uid): return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/ill/display%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {})), norobot=True) _ = gettext_set_language(argd['ln']) user_info = collect_user_info(req) if not user_info['precached_useloans']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use ill.")) body = display_ill_form(ln=argd['ln']) return page(title = _("Interlibrary loan request for books"), body = body, uid = uid, lastupdated = __lastupdated__, req = req, language = argd['ln'], navmenuid = "ill") def register_request(self, req, form): """ Displays all loans of a given user @param ln: language @return the page for inbox """ argd = wash_urlargd(form, {'ln': (str, ""), 'title': (str, ""), 'authors': (str, ""), 'place': (str, ""), 'publisher': (str, ""), 'year': (str, ""), 'edition': (str, ""), 'isbn': (str, ""), 'period_of_interest_from': (str, ""), 'period_of_interest_to': (str, ""), 'additional_comments': (str, ""), 'conditions': (str, ""), 'only_edition': (str, ""), }) # Check if user is logged uid = getUid(req) if CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "%s/ill/register_request" % \ (CFG_SITE_URL,), navmenuid="ill") elif uid == -1 or isGuestUser(uid): return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/ill/register_request%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {})), norobot=True) _ = gettext_set_language(argd['ln']) user_info = collect_user_info(req) if not user_info['precached_useloans']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use ill.")) body = ill_register_request(uid=uid, title=argd['title'], authors=argd['authors'], place=argd['place'], publisher=argd['publisher'], year=argd['year'], edition=argd['edition'], isbn=argd['isbn'], period_of_interest_from = argd['period_of_interest_from'], period_of_interest_to = argd['period_of_interest_to'], additional_comments = argd['additional_comments'], conditions = argd['conditions'], only_edition = argd['only_edition'], request_type='book', ln=argd['ln']) return page(title = _("Interlibrary loan request for books"), body = body, uid = uid, lastupdated = __lastupdated__, req = req, language = argd['ln'], navmenuid = "ill") class WebInterfaceHoldingsPages(WebInterfaceDirectory): """Defines the set of /holdings pages.""" _exports = ['', 'display', 'request', 'send', 'ill_request_with_recid', 'ill_register_request_with_recid'] def __init__(self, recid=-1): self.recid = recid def index(self, req, form): """ Redirects to display function """ return self.display(req, form) def display(self, req, form): """ Show the tab 'holdings'. """ argd = wash_urlargd(form, {'do': (str, "od"), 'ds': (str, "all"), 'nb': (int, 100), 'p': (int, 1), 'voted': (int, -1), 'reported': (int, -1), }) _ = gettext_set_language(argd['ln']) record_exists_p = record_exists(self.recid) if record_exists_p != 1: if record_exists_p == -1: msg = _("The record has been deleted.") else: msg = _("Requested record does not seem to exist.") msg = '' + msg + '' title, description, keywords = \ websearch_templates.tmpl_record_page_header_content(req, self.recid, argd['ln']) return page(title = title, show_title_p = False, body = msg, description = description, keywords = keywords, uid = getUid(req), language = argd['ln'], req = req, navmenuid='search') body = perform_get_holdings_information(self.recid, req, argd['ln']) uid = getUid(req) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_record(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) unordered_tabs = get_detailed_page_tabs(get_colID(guess_primary_collection_of_a_record(self.recid)), self.recid, ln=argd['ln']) ordered_tabs_id = [(tab_id, values['order']) for (tab_id, values) in unordered_tabs.iteritems()] ordered_tabs_id.sort(lambda x, y: cmp(x[1], y[1])) link_ln = '' if argd['ln'] != CFG_SITE_LANG: link_ln = '?ln=%s' % argd['ln'] tabs = [(unordered_tabs[tab_id]['label'], \ '%s/record/%s/%s%s' % (CFG_SITE_URL, self.recid, tab_id, link_ln), \ tab_id in ['holdings'], unordered_tabs[tab_id]['enabled']) \ for (tab_id, _order) in ordered_tabs_id if unordered_tabs[tab_id]['visible'] == True] top = webstyle_templates.detailed_record_container_top(self.recid, tabs, argd['ln']) bottom = webstyle_templates.detailed_record_container_bottom(self.recid, tabs, argd['ln']) title = websearch_templates.tmpl_record_page_header_content(req, self.recid, argd['ln'])[0] navtrail = create_navtrail_links(cc=guess_primary_collection_of_a_record(self.recid), ln=argd['ln']) navtrail += ' > '% (CFG_SITE_URL, self.recid, argd['ln']) navtrail += title navtrail += '' return pageheaderonly(title=title, navtrail=navtrail, uid=uid, verbose=1, req=req, metaheaderadd = "" % CFG_SITE_URL, language=argd['ln'], navmenuid='search', navtrail_append_title_p=0) + \ websearch_templates.tmpl_search_pagestart(argd['ln']) + \ top + body + bottom + \ websearch_templates.tmpl_search_pageend(argd['ln']) + \ pagefooteronly(lastupdated=__lastupdated__, language=argd['ln'], req=req) # Return the same page wether we ask for /record/123 or /record/123/ __call__ = index def request(self, req, form): """ Show new hold request form. """ argd = wash_urlargd(form, {'ln': (str, ""), 'barcode': (str, "")}) _ = gettext_set_language(argd['ln']) uid = getUid(req) body = perform_new_request(recid=self.recid, barcode=argd['barcode'], ln=argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../holdings/request", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/record/%s/holdings/request%s" % ( CFG_SITE_URL, self.recid, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {})), norobot=True) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_record(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) unordered_tabs = get_detailed_page_tabs(get_colID(guess_primary_collection_of_a_record(self.recid)), self.recid, ln=argd['ln']) ordered_tabs_id = [(tab_id, values['order']) for (tab_id, values) in unordered_tabs.iteritems()] ordered_tabs_id.sort(lambda x, y: cmp(x[1], y[1])) link_ln = '' if argd['ln'] != CFG_SITE_LANG: link_ln = '?ln=%s' % argd['ln'] tabs = [(unordered_tabs[tab_id]['label'], \ '%s/record/%s/%s%s' % (CFG_SITE_URL, self.recid, tab_id, link_ln), \ tab_id in ['holdings'], unordered_tabs[tab_id]['enabled']) \ for (tab_id, _order) in ordered_tabs_id if unordered_tabs[tab_id]['visible'] == True] top = webstyle_templates.detailed_record_container_top(self.recid, tabs, argd['ln']) bottom = webstyle_templates.detailed_record_container_bottom(self.recid, tabs, argd['ln']) title = websearch_templates.tmpl_record_page_header_content(req, self.recid, argd['ln'])[0] navtrail = create_navtrail_links(cc=guess_primary_collection_of_a_record(self.recid), ln=argd['ln']) navtrail += ' > '% (CFG_SITE_URL, self.recid, argd['ln']) navtrail += title navtrail += '' return pageheaderonly(title=title, navtrail=navtrail, uid=uid, verbose=1, req=req, metaheaderadd = "" % CFG_SITE_URL, language=argd['ln'], navmenuid='search', navtrail_append_title_p=0) + \ websearch_templates.tmpl_search_pagestart(argd['ln']) + \ top + body + bottom + \ websearch_templates.tmpl_search_pageend(argd['ln']) + \ pagefooteronly(lastupdated=__lastupdated__, language=argd['ln'], req=req) def send(self, req, form): """ Create a new hold request. """ argd = wash_urlargd(form, {'period_from': (str, ""), 'period_to': (str, ""), 'barcode': (str, "") }) uid = getUid(req) body = perform_new_request_send(recid=self.recid, uid=uid, period_from=argd['period_from'], period_to=argd['period_to'], barcode=argd['barcode']) ln = CFG_SITE_LANG _ = gettext_set_language(ln) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_record(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) unordered_tabs = get_detailed_page_tabs(get_colID(guess_primary_collection_of_a_record(self.recid)), self.recid, ln=ln) ordered_tabs_id = [(tab_id, values['order']) for (tab_id, values) in unordered_tabs.iteritems()] ordered_tabs_id.sort(lambda x, y: cmp(x[1], y[1])) link_ln = '' if argd['ln'] != CFG_SITE_LANG: link_ln = '?ln=%s' % ln tabs = [(unordered_tabs[tab_id]['label'], \ '%s/record/%s/%s%s' % (CFG_SITE_URL, self.recid, tab_id, link_ln), \ tab_id in ['holdings'], unordered_tabs[tab_id]['enabled']) \ for (tab_id, _order) in ordered_tabs_id if unordered_tabs[tab_id]['visible'] == True] top = webstyle_templates.detailed_record_container_top(self.recid, tabs, argd['ln']) bottom = webstyle_templates.detailed_record_container_bottom(self.recid, tabs, argd['ln']) title = websearch_templates.tmpl_record_page_header_content(req, self.recid, argd['ln'])[0] navtrail = create_navtrail_links(cc=guess_primary_collection_of_a_record(self.recid), ln=argd['ln']) navtrail += ' > '% (CFG_SITE_URL, self.recid, argd['ln']) navtrail += title navtrail += '' return pageheaderonly(title=title, navtrail=navtrail, uid=uid, verbose=1, req=req, language=argd['ln'], navmenuid='search', navtrail_append_title_p=0) + \ websearch_templates.tmpl_search_pagestart(argd['ln']) + \ top + body + bottom + \ websearch_templates.tmpl_search_pageend(argd['ln']) + \ pagefooteronly(lastupdated=__lastupdated__, language=argd['ln'], req=req) def ill_request_with_recid(self, req, form): """ Show ILL request form. """ argd = wash_urlargd(form, {'ln': (str, "")}) _ = gettext_set_language(argd['ln']) uid = getUid(req) body = ill_request_with_recid(recid=self.recid, ln=argd['ln']) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../holdings/ill_request_with_recid", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/record/%s/holdings/ill_request_with_recid%s" % ( CFG_SITE_URL, self.recid, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_record(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) unordered_tabs = get_detailed_page_tabs(get_colID(guess_primary_collection_of_a_record(self.recid)), self.recid, ln=argd['ln']) ordered_tabs_id = [(tab_id, values['order']) for (tab_id, values) in unordered_tabs.iteritems()] ordered_tabs_id.sort(lambda x, y: cmp(x[1], y[1])) link_ln = '' if argd['ln'] != CFG_SITE_LANG: link_ln = '?ln=%s' % argd['ln'] tabs = [(unordered_tabs[tab_id]['label'], \ '%s/record/%s/%s%s' % (CFG_SITE_URL, self.recid, tab_id, link_ln), \ tab_id in ['holdings'], unordered_tabs[tab_id]['enabled']) \ for (tab_id, _order) in ordered_tabs_id if unordered_tabs[tab_id]['visible'] == True] top = webstyle_templates.detailed_record_container_top(self.recid, tabs, argd['ln']) bottom = webstyle_templates.detailed_record_container_bottom(self.recid, tabs, argd['ln']) title = websearch_templates.tmpl_record_page_header_content(req, self.recid, argd['ln'])[0] navtrail = create_navtrail_links(cc=guess_primary_collection_of_a_record(self.recid), ln=argd['ln']) navtrail += ' > '% (CFG_SITE_URL, self.recid, argd['ln']) navtrail += title navtrail += '' return pageheaderonly(title=title, navtrail=navtrail, uid=uid, verbose=1, req=req, metaheaderadd = "" % CFG_SITE_URL, language=argd['ln'], navmenuid='search', navtrail_append_title_p=0) + \ websearch_templates.tmpl_search_pagestart(argd['ln']) + \ top + body + bottom + \ websearch_templates.tmpl_search_pageend(argd['ln']) + \ pagefooteronly(lastupdated=__lastupdated__, language=argd['ln'], req=req) def ill_register_request_with_recid(self, req, form): """ Register ILL request. """ argd = wash_urlargd(form, {'ln': (str, ""), 'period_of_interest_from': (str, ""), 'period_of_interest_to': (str, ""), 'additional_comments': (str, ""), 'conditions': (str, ""), 'only_edition': (str, ""), }) _ = gettext_set_language(argd['ln']) uid = getUid(req) body = ill_register_request_with_recid(recid=self.recid, uid=uid, period_of_interest_from = argd['period_of_interest_from'], period_of_interest_to = argd['period_of_interest_to'], additional_comments = argd['additional_comments'], conditions = argd['conditions'], only_edition = argd['only_edition'], ln=argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../holdings/ill_request_with_recid", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/record/%s/holdings/ill_request_with_recid%s" % ( CFG_SITE_URL, self.recid, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_record(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) unordered_tabs = get_detailed_page_tabs(get_colID(guess_primary_collection_of_a_record(self.recid)), self.recid, ln=argd['ln']) ordered_tabs_id = [(tab_id, values['order']) for (tab_id, values) in unordered_tabs.iteritems()] ordered_tabs_id.sort(lambda x, y: cmp(x[1], y[1])) link_ln = '' if argd['ln'] != CFG_SITE_LANG: link_ln = '?ln=%s' % argd['ln'] tabs = [(unordered_tabs[tab_id]['label'], \ '%s/record/%s/%s%s' % (CFG_SITE_URL, self.recid, tab_id, link_ln), \ tab_id in ['holdings'], unordered_tabs[tab_id]['enabled']) \ for (tab_id, _order) in ordered_tabs_id if unordered_tabs[tab_id]['visible'] == True] top = webstyle_templates.detailed_record_container_top(self.recid, tabs, argd['ln']) bottom = webstyle_templates.detailed_record_container_bottom(self.recid, tabs, argd['ln']) title = websearch_templates.tmpl_record_page_header_content(req, self.recid, argd['ln'])[0] navtrail = create_navtrail_links(cc=guess_primary_collection_of_a_record(self.recid), ln=argd['ln']) navtrail += ' > '% (CFG_SITE_URL, self.recid, argd['ln']) navtrail += title navtrail += '' return pageheaderonly(title=title, navtrail=navtrail, uid=uid, verbose=1, req=req, language=argd['ln'], navmenuid='search', navtrail_append_title_p=0) + \ websearch_templates.tmpl_search_pagestart(argd['ln']) + \ top + body + bottom + \ websearch_templates.tmpl_search_pageend(argd['ln']) + \ pagefooteronly(lastupdated=__lastupdated__, language=argd['ln'], req=req) diff --git a/modules/bibformat/doc/hacking/bibformat-api.webdoc b/modules/bibformat/doc/hacking/bibformat-api.webdoc index 91fe267fe..bbfd497fe 100644 --- a/modules/bibformat/doc/hacking/bibformat-api.webdoc +++ b/modules/bibformat/doc/hacking/bibformat-api.webdoc @@ -1,891 +1,889 @@ ## -*- mode: html; coding: utf-8; -*- ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 ****************************************************************************
 ** IMPORTANT NOTE: Note that this documentation is an updated version of  **
 ** an earlier technical draft of BibFormat specifications. Please first   **
 ** refer to the BibFormat admin guide.                                    **
 ****************************************************************************
 
 Technical Overview of the new BibFormat
 =======================================
 
 Contents:
 1. Python API
 2. The philosophy behind BibFormat
 3. Differences between the old PHP version and the new Pythonic version
 4. Migrating from the previous PHP BibFormat version to the new Pythonic version
 5. Specifications of the new BibFormat configuration files.
 
 
 1. Python API
 
 The APIs of bibformat.py consists in these functions:
 
  def format_record(recID, of, ln=CFG_SITE_LANG, verbose=0,
                    search_pattern=None, xml_record=None, user_info=None,
                    on_the_fly=False):
      """
      Formats a record given its ID (or its XML representation)
      and an output format.
 
      Returns a formatted version of the record in the specified
      language, with pattern context, and specified output format.
      The function will define by itself which format template must be
      applied.
 
      Parameters that allow contextual formatting (like 'search_pattern'
      and 'user_info') are useful only when doing on-the-fly
      formatting, or when caching with care (e.g. caching all formatted
      versions of a record for each possible 'ln').
 
      The arguments are as follows:
 
                recID -  the ID of the record to format. If ID does not exist
                         the function returns empty string or an error
                         string, depending on level of verbosity.
                         If 'xml_record' parameter is specified, 'recID'
                         is ignored
 
                   of -  an output format code. If 'of' does not exist as code in
                         output format, the function returns empty
                         string or an error string, depending on level
                         of verbosity.  ;of' is case insensitive.
 
                   ln -  the language to use to format the record. If
                         'ln' is an unknown language, or translation
                         does not exist, default CFG_SITE_LANG language
                         will be applied whenever possible.
                         Allows contextual formatting.
 
              verbose -  the level of verbosity in case of errors/warnings
                         0 - Silent mode
                         5 - Prints only errors
                         9 - Prints errors and warnings
 
       search_pattern -  the pattern used as search query when asked to
                         format this record (User request in web
                         interface). Allows contextual formatting.
 
           xml_record -  an XML string representation of the record to
                         format.  If it is specified, recID parameter is
                         ignored. The XML must be pasable by BibRecord.
 
            user_info - allows to grant access to some functionalities
                        on a page depending on the user's
                        priviledges. 'user_info' is the same structure
                        as the one returned by webuser.collect_user_info(req),
                        (that is a dictionary).
 
           on_the_fly - if False, try to return an already preformatted
                        version of the record in the database.
 
      """
 
 
  Example:
    >> from invenio.bibformat import format_record
    >> format_record(5, "hb", "fr")
 
 
  def format_records(recIDs, of, ln=CFG_SITE_LANG, verbose=0, search_pattern=None,
                     xml_records=None, user_info=None, record_prefix=None,
                     record_separator=None, record_suffix=None,
                     prologue="", epilogue="", req=None, on_the_fly=False):
      """
      Returns a list of formatted records given by a list of record IDs or a
      list of records as xml.
      Adds a prefix before each record, a suffix after each record,
      plus a separator between records.
 
      Also add optional prologue and epilogue to the complete formatted list.
 
      You can either specify a list of record IDs to format, or a list of
      xml records, but not both (if both are specified recIDs is ignored).
 
      'record_separator' is a function that returns a string as separator between
      records. The function must take an integer as unique parameter,
      which is the index in recIDs (or xml_records) of the record that has
      just been formatted. For example separator(i) must return the separator
      between recID[i] and recID[i+1]. Alternatively separator can be a single
      string, which will be used to separate all formatted records.
      The same applies to 'record_prefix' and 'record_suffix'.
 
      'req' is an optional parameter on which the result of the function
      are printed lively (prints records after records) if it is given.
      Note that you should set 'req' content-type by yourself, and send
      http header before calling this function as it will not do it.
 
      This function takes the same parameters as 'format_record' except for:
 
                recIDs -  a list of record IDs to format
 
           xml_records -  a list of xml string representions of the records to
                          format. If this list is specified, 'recIDs' is ignored.
 
         record_prefix - a string or a function the takes the index of the record
                         in 'recIDs' or 'xml_records' for which the function must
                         return a string.
                         Printed before each formatted record.
 
      record_separator - either a string or a function that returns string to
                         separate formatted records. The function takes the index
                         of the record in 'recIDs' or 'xml_records' that is being
                         formatted.
 
         record_prefix - a string or a function the takes the index of the record
                         in 'recIDs' or 'xml_records' for which the function must
                         return a string.
                         Printed after each formatted record
 
                   req - an optional request object on which formatted records
                         can be printed (for "live" output )
 
              prologue - a string printed before all formatted records string
 
              epilogue - a string printed after all formatted records string
 
            on_the_fly - if False, try to return an already preformatted version
                         of the records in the database
      """
 
 
  def get_output_format_content_type(of):
      """
      Returns the content type (eg. 'text/html' or 'application/ms-excel') \
      of the given output format.
 
      The function takes this mandatory parameter:
 
      of - the code of output format for which we want to get the content type
      """
 
 
  def record_get_xml(recID, format='xm', decompress=zlib.decompress):
      """
      Returns an XML string of the record given by recID.
 
      The function builds the XML directly from the database,
      without using the standard formatting process.
 
      'format' allows to define the flavour of XML:
         - 'xm' for standard XML
         - 'marcxml' for MARC XML
         - 'oai_dc' for OAI Dublin Core
         - 'xd' for XML Dublin Core
 
      If record does not exist, returns empty string.
 
      The function takes the following parameters:
 
           recID - the id of the record to retrieve
 
          format - the XML flavor in which we want to get the record
 
      decompress _ a function used to decompress the record from the database
     """
 
 The API of the BibFormat Object ('bfo') given as a parameter to
 format function of format elements consist in the following
 functions. This API is to be used only inside format elements.
 
  def control_field(self, tag, escape='0'):
     """
     Returns the value of control field given by tag in record.
 
     If the value does not exist, returns empty string
     The returned value is always a string.
 
     'escape' parameter allows to escape special characters
     of the field. The value of escape can be:
           0 - no escaping
           1 - escape all HTML characters
           2 - remove unsafe HTML tags (Eg. keep <br />)
           3 - Mix of mode 1 and 2. If value of field starts with
               <!-- HTML -->, then use mode 2. Else use mode 1.
           4 - Remove all HTML tags
           5 - Same as 2, with more tags allowed (like <img>)
           6 - Same as 3, with more tags allowed (like <img>)
 
     The arguments are:
 
          tag    -  the marc code of a field
          escape -  1 if returned value should be escaped. Else 0.
                    (see above for other modes)
     """
 
  def field(self, tag, escape='0'):
     """
     Returns the value of the field corresponding to tag in the
     current record.
 
     If the value does not exist, returns empty string
     Else returns the same as bfo.fields(..)[0] (see docstring below).
 
     'escape' parameter allows to escape special characters
     of the field. The value of escape can be:
           0 - no escaping
           1 - escape all HTML characters
           2 - remove unsafe HTML tags (Eg. keep <br />)
           3 - Mix of mode 1 and 2. If value of field starts with
               <!-- HTML -->, then use mode 2. Else use mode 1.
           4 - Remove all HTML tags
           5 - Same as 2, with more tags allowed (like <img>)
           6 - Same as 3, with more tags allowed (like <img>)
 
     The arguments are:
 
          tag  -  the marc code of a field
          escape -  1 if returned value should be escaped. Else 0.
                    (see above for other modes)
     """
 
 
  def fields(self, tag, escape='0', repeatable_subfields_p=False):
     """
     Returns the list of values corresonding to "tag".
 
     If tag has an undefined subcode (such as 999C5),
     the function returns a list of dictionaries, whoose keys
     are the subcodes and the values are the values of tag.subcode.
     If the tag has a subcode, simply returns list of values
     corresponding to tag.
     Eg. for given MARC:
         999C5 $a value_1a $b value_1b
         999C5 $b value_2b
         999C5 $b value_3b $b value_3b_bis
 
         >> bfo.fields('999C5b')
         >> ['value_1b', 'value_2b', 'value_3b', 'value_3b_bis']
         >> bfo.fields('999C5')
         >> [{'a':'value_1a', 'b':'value_1b'},
             {'b':'value_2b'},
             {'b':'value_3b'}]
     By default the function returns only one value for each
     subfield (that is it considers that repeatable subfields are
     not allowed). It is why in the above example 'value3b_bis' is
     not shown for bfo.fields('999C5').  (Note that it is not
     defined which of value_3b or value_3b_bis is returned).  This
     is to simplify the use of the function, as most of the time
     subfields are not repeatable (in that way we get a string
     instead of a list).  You can allow repeatable subfields by
     setting 'repeatable_subfields_p' parameter to True. In
     this mode, the above example would return:
         >> bfo.fields('999C5b', repeatable_subfields_p=True)
         >> ['value_1b', 'value_2b', 'value_3b']
         >> bfo.fields('999C5', repeatable_subfields_p=True)
         >> [{'a':['value_1a'], 'b':['value_1b']},
             {'b':['value_2b']},
             {'b':['value_3b', 'value3b_bis']}]
     NOTICE THAT THE RETURNED STRUCTURE IS DIFFERENT.  Also note
     that whatever the value of 'repeatable_subfields_p' is,
     bfo.fields('999C5b') always show all fields, even repeatable
     ones. This is because the parameter has no impact on the
     returned structure (it is always a list).
 
     'escape' parameter allows to escape special characters
      of the fields. The value of escape can be:
                   0 - no escaping
                   1 - escape all HTML characters
                   2 - remove unsafe HTML tags (Eg. keep <br />)
                   3 - Mix of mode 1 and 2. If value of field starts with
                       <!-- HTML -->, then use mode 2. Else use mode 1.
                   4 - Remove all HTML tags
                   5 - Same as 2, with more tags allowed (like <img>)
                   6 - Same as 3, with more tags allowed (like <img>)
 
     The arguments are:
 
           tag  -  the marc code of a field
           escape -  1 if returned value should be escaped. Else 0.
                    (see above for other modes)
           repeatable_subfields_p - if True, returns the list of
                                    subfields in the dictionary @return
                                    values of field tag in record """
 
  def kb(self, kb, string, default=""):
     """
     Returns the value of the "string" in the knowledge base "kb".
 
     If kb does not exist or string does not exist in kb,
     returns 'default' string or empty string if not specified
 
     The arguments are as follows:
 
           kb  -  the knowledge base name in which we want to find the mapping.
                  If it does not exist the function returns the original
                  'string' parameter value. The name is case insensitive (Uses
                  the SQL 'LIKE' syntax to retrieve value).
 
       string  -  the value for which we want to find a translation-
                  If it does not exist the function returns 'default' string.
                  The string is case insensitive (Uses the SQL 'LIKE' syntax
                  to retrieve value).
 
      default  -  a default value returned if 'string' not found in 'kb'.
 
     """
 
  def get_record(self):
     """
     Returns the record encapsulated in bfo as a BibRecord structure.
     You can get full access to the record through bibrecord.py functions.
     """
 
   Example (from inside BibFormat element):
   >> bfo.field("520.a")
   >> 'We present a quantitative appraisal of the physics potential
       for neutrino experiments.'
   >>
   >> bfo.control_field("001")
   >> '12'
   >>
   >> bfo.fields("700.a")
   >>['Alekhin, S I', 'Anselmino, M', 'Ball, R D', 'Boglione, M']
   >>
   >> bfo.kb("DBCOLLID2COLL", "ARTICLE")
   >> 'Published Article'
   >>
   >> bfo.kb("DBCOLLID2COLL", "not in kb", "My Value")
   >> 'My Value'
 
 Moreover you can have access to the language requested for the
 formatting, the search pattern used by the user in the web
 interface and the userID by directly getting the attribute from 'bfo':
 
     bfo.lang
     """
     Returns the language that was asked to be used for the
     formatting. Always returns a string.
     """
 
     bfo.search_pattern
     """
     Returns the search pattern specified by the user when
     the record had to be formatted. Always returns a string.
     """
 
     bfo.user_info
     """
     Returns a dictionary with information about current user.
     The returned dictionary has the following structure:
         user_info = {
             'remote_ip' : '',
             'remote_host' : '',
             'referer' : '',
             'uri' : '',
             'agent' : '',
-            'apache_user' : '',
-            'apache_group' : [],
             'uid' : -1,
             'nickname' : '',
             'email' : '',
             'group' : [],
             'guest' : '1'
         }
     """
 
     bfo.uid
     """
     ! DEPRECATED: use bfo.user_info['uid'] instead
     """
 
     bfo.recID
     """
     Returns the id of the record
     """
 
     bfo.req
     """
     ! DEPRECATED: use bfo.user_info instead
     """
 
     bfo.format
     """
     ! DEPRECATED: use bfo.output_format instead
     """
 
     bfo.output_format
     """
     Returns the format in which the record is being formatted
     """
 
   Example (from inside BibFormat element):
   >> bfo.lang
   >> 'en'
   >>
   >> bfo.search_pattern
   >> 'mangano and neutrino and factory'
 
 
 2. The philosophy behind BibFormat
 
 BibFormat is in charge of formatting the bibliographic records that
 are displayed to your users. As you potentially have a huge amount of
 bibliographic records, you cannot specify manually for each of them
 how it should be formatted. This is why you can define rules that will
 allow BibFormat to understand which kind of formatting to apply to a given
 record. You define this set of rules in what is called an "output
 format".
 
 You can have different output formats, each with its own characteristics.
 For example you certainly want that when multiple bibliographic records are
 displayed at the same time (as it happens in search results), only
 short versions are shown to the user, while a detailed record is
 preferable when a single record is displayed. You might also want to
 let your users decide which kind of output they want. For example you
 might need to display HTML for regular web browsing, but would also
 give a BibTeX version of the bibliographic reference for direct
 inclusion in a LaTeX document.
 See section 5.1 to learn how to create or modify output formats.
 
 While output formats define what kind of formatting must be applied,
 they do not define HOW the formatting is done. This is the role of the
 "format templates", which define the layout and look of a
 bibliographic reference. These format templates are rather easy to
 write if you know a little bit of HTML (see section 5.2 "Format
 templates specifications"). You will certainly have to create
 different format templates, for different kinds of records. For
 example you might want records that contain pictures to display them,
 maybe with captions, while records that do not have pictures limited
 to printing a title and an abstract.
 
 In summary, you have different output formats (like 'brief HTML',
 'detailed HTML' or 'BibTeX') that call different format templates
 according to some criteria.
 
 There is still one kind of configuration file that we have not talked
 about: the "format elements". These are the "bricks" that you use in
 format templates, to get the values of a record. You will learn to use
 them in your format template in section 5.2 "Format templates
 specifications", but you will mostly not need to modify them or create
 new ones. However if you do need to edit one, read section 5.3 "Format
 elements specifications" (And if you know Python it will be easy, as
 they are written in Python).
 
 Finally BibFormat can make use of mapping tables called "knowledge
 bases". Their primary use is to act like a translation table, to
 normalize records before displaying them. For example, you can say
 that records that have value "Phys Rev D" or "Physical Review D" for
 field "published in" must display "Phys Rev : D." to users. See
 section 5.4 to learn how to edit knowledge bases.
 
 In summary, there are three layers.  Output formats:
 
 +-----------------------------------------------------+
 |                    Output Format                    | (Layer 1)
 |                    eg: HTML_Brief.bfo               |
 +-----------------------------------------------------+
 
 call one of several `format templates':
 
 +-------------------------+ +-------------------------+
 |     Format Template     | |     Format Template     | (Layer 2)
 |     eg: preprint.bft    | |     eg: default.bft     |
 +-------------------------+ +-------------------------+
 
 that use one or several format elements:
 
 +--------------+ +----------------+ +-----------------+
 |Format Element| |Format Element  | | Format Element  | (Layer 3)
 |eg: authors.py| |eg: abstract.py | | eg: title.py    |
 +--------------+ +----------------+ +-----------------+
 
 
 3. Differences between the old PHP version and the new Pythonic version
 
 The most noticeable differences are:
 
  a) "Behaviours" have been renamed "Output formats".
  b) "Formats" have been renamed "Format templates". They are now
      written in HTML.
  c) "User defined functions" have been dropped.
  d) "Extraction rules" have been dropped.
  e) "Link rules" have been dropped.
  f) "File formats" have been dropped.
  g) "Format elements" have been introduced. They are written in Python,
      and can simulate c), d) and e).
  h)  Formats can be managed through web interface or through
      human-readable config files.
  i)  Introduction of tools like validator and dependencies checker.
  j)  Better support for multi-language formatting.
 
 Some of the advantages are:
 
  + Management of formats is much clearer and easier (less concepts,
    more tools).
  + Writing formats is easier to learn : less concepts
    to learn, redesigned work-flow, use of existing well known and
    well documented languages.
  + Editing formats is easier: You can use your preferred HTML editor such as
    Emacs, Dreamweaver or Frontpage to modify templates, or any text
    editor for output formats and format elements. You can also use the
    simplified web administration interface.
  + Faster and more powerful templating system.
  + Separation of business logic (output formats, format elements)
    and presentation layer (format templates). This makes the management
    of formats simpler.
 
 The disadvantages are:
 
  - No backward compatibility with old formats.
  - Stricter separation of business logic and presentation layer:
    no more use of statements such as if(), forall() inside templates,
    and this requires more work to put logic inside format elements.
 
 
 4. Migrating from the previous PHP BibFormat version to the new Pythonic version
 
 Old BibFormat formats are no longer compatible with the new BibFormat
 files. If you have not modified the "Formats" or modified only a
 little bit the "Behaviours", then the transition will be painless and
 automatic. Otherwise you will have to manually rewrite some of the
 formats. This should however not be a big problem. Firstly because the
 Invenio installation will provide both versions of BibFormat for
 some time. Secondly because both BibFormat versions can run side by
 side, so that you can migrate your formats while your server still
 works with the old formats.  Thirdly because we provide a migration
 kit that can help you go through this process. Finally because the
 migration is not so difficult, and because it will be much easier for
 you to customize how BibFormat formats your bibliographic data.
 
 Concerning the migration kit it can:
  a) Effortlessly migrate your behaviours, unless they include complex
     logic, which usually they don't.
  b) Help you migrate formats to format templates and format elements.
  c) Effortlessly migrate your knowledge bases.
 
 Point b) is the most difficult to achieve: previous formats did mix
 business logic and code for the presentation, and could use PHP
 functions. The new BibFormat separates business logic and
 presentation, and does not support PHP. The transition kit will try to
 move business logic to the format elements, and the presentation to
 the format templates. These files will be created for you, includes
 the original code and, if possible, a proposal of Python
 translation. We recommend that you do not to use the transition kit to
 translate formats, especially if you have not modified default
 formats, or only modified default formats in some limited places. You
 will get cleaner code if you write format elements and format
 templates yourself.
 
 
 5. Specifications of the new BibFormat configuration files.
 
    BibFormat uses human readable configuration files. However (apart
    from format elements) these files can be edited and managed through
    a web interface.
 
 5.1 Output formats specifications
 
 Output formats specify rules that define which format template
 to use to format a record.
 While the syntax of output formats is basic, we recommend that you use
 the web interface do edit them, to be sure that you make no error.
 
 The syntax of output format is the following one. First you
 define which field code you put as the conditon for the rule.
 You suffix it with a column. Then on next lines, define the values of
 the condition, followed by --- and then the filename of the template
 to use:
 
   tag 980.a:
   PICTURE --- PICTURE_HTML_BRIEF.bft
   PREPRINT --- PREPRINT_HTML_BRIEF.bft
   PUBLICATION --- PUBLICATION_HTML_BRIEF.bft
 
 This means that if value of field 980.a is equal to PICTURE, then we
 will use format template PICTURE_HTML_BRIEF.bft. Note that you must
 use the filename of the template, not the name. Also note that spaces
 at the end or beginning are not considered. On the following lines,
 you can either put other conditions on tag 980.a, or add another tag on
 which you want to put conditions.
 
 At the end you can add a default condition:
 
    default: PREPRINT_HTML_BRIEF.bft
 
 which means that if no condition is matched, a format suitable for
 Preprints will be used to format the current record.
 
 The output format file could then look like this:
 
   tag 980.a:
   PICTURE --- PICTURE_HTML_BRIEF.bft
   PREPRINT --- PREPRINT_HTML_BRIEF.bft
   PUBLICATION --- PUBLICATION_HTML_BRIEF.bft
 
   tag 8560.f:
   .*@cern.ch --- SPECIAL_MEMBER_FORMATTING.bft
 
   default: PREPRINT_HTML_BRIEF.bft
 
 You can add as many rules as you want. Keep in mind that they are read
 in the order they are defined, and that only first rule that
 matches will be used.
 Notice the condition on tag 8560.f: it uses a regular expression to
 match any email address that ends with @cern.ch (the regular
 expression must be understandable by Python)
 
 Some other considerations on the management of output formats:
 - Format outputs must be placed inside directory
   /etc/bibformat/outputs/ of your Invenio installation.
 - Note that as long as you have not provided a name to an output
   THROUGH the web interface, it will not be available as a choice
   for your users in some parts of Invenio.
 - You should remove output formats THROUGH the web interface.
 - The format extension of output format is .bfo
 
 
 5.2 Format templates specifications
 
 Format templates are written in HTML-like syntax. You can use the
 standard HTML and CSS markup languague to do the formatting. The best
 thing to do is to create a page in your favourite editor, and once you
 are glad with it, add the dynamic part of the page, that is print the
 fields of the records. Let's say you have defined this page:
 
   <h1>Some title</h1>
   <p><i>Abstract: </i>Some abstract</p>
 
 Then you want that instead of "Some title" and "Some abstract", the
 value of the current record that is being displayed is used. To do so,
 you must use a format element brick. Either you know the name of the
 brick by heart, or you look for it in the elements documentation (see
 section 5.3). For example you would find there that you can print the
 title of the record by writting the HTML tag <BFE_TITLE /> in your
 format template, with parameter 'default' for a default value.
 
   <h1><BFE_TITLE default="No Title"/></h1>
   <p><BFE_ABSTRACT limit="1" prefix="<i>Abstract: </i>"
   default="No abstract"/></p>
 
 Notice that <BFE_ABSTRACT /> has a parameter "limit" that <BFE_title/>
  had not ("limit" allows to limit the number of sentences of the
 abstract, according to the documentation). Note that while format
 elements might have different parameters, they always can take the the
 three following ones: "prefix" and "suffix", whose values are printed
 only if the element is not empty, and "default", which is printed only
 if element is an empty string. We have used "prefix" for the abstract,
 so that the label "<i>Abstract: </i>" is only printed if the record
 has an abstract.
 
 You should also provide these tags in all of your templates:
  -<name>a name for this template in the admin web interface</name>
  -<description>a description to be used in admin web interface for
   this template</description>
 
 Another feature of the templates is the support for multi-languages
 outputs. You can include <lang> tags, which contain tags labeled with
 the names of the languages supported in Invenio. For example, one
 might write:
 
   <lang><en>A record:</en><fr>Une notice:</fr></lang>
   <h1><BFE_TITLE default="No Title"/></h1>
   <p><BFE_ABSTRACT limit="1" prefix="<i>Abstract: </i>"
   default="No abstract"/></p>
 
 When doing this you should at least make sure that the default
 language of your server installation is available in each <lang>
 tag. It is the one that is used if the requested language to display
 the record is not available. Note that we could also provide a
 translation in a similar way for the "No Title" default value inside
 <BFE_Title /> tag.
 
 Some other considerations on the use of elements inside templates:
  -Format elements names are not case sensitive
  -Format elements names always start with <BFE_
  -Format elements parameters can contain '<' characters,
   and quotes different from the kind that delimit parameters (you can
   for example have <BFE_Title default='<a href="#">No Title</a>'/> )
  -Format templates must be placed inside the directory
   /etc/bibformat/templates/ of your Invenio installation
  -The format extension of a template is .bft
 
 Trick: you can use the <BFE_FIELD tag="245__a" /> to print the value
 of any field 245 $a in your templates.  This practice is however not
 recommended because it would necessitate to revise all format
 templates if you change meaning of the MARC code schema.
 
 5.3 Format elements specifications
 
 Format elements are the bricks used in format templates to provide the
 dynamic contents inside format templates.
 
 For the most basic format elements, you do not even need to write
 them: as long as you define `tag names' for MARC tags in the BibIndex
 Admin's Manage logical fields interface (database table tag),
 BibFormat knows which field must be printed when <BFE_tag_name/> is
 used inside a template.
 
 However for more complex processing, you will need to write a format
 element. A format element is written in Python. Therefore its file
 extension is ".py". The name you choose for the file is the one that
 will be used inside format template to call the element, so choose it
 carefully such that it is not too long, but self explanatory (you can
 prefix the filename with BFE or not, but the element will always be
 called with prefix <BFE_ inside templates).  Then you just need to
 drop the file in the lib/python/invenio/bibformat_elements/ directory
 of your Invenio installation. Inside your file you have to define
 a function named "format_element", which takes at least a "bfo"
 parameter (bfo for BibFormat Object). The function must return a
 string:
 
   def format_element(bfo):
       out = ""
 
       return out
 
 You can have as many parameters as you want, as long as you make sure
 that parameter bfo is here. Let's see how to define an element that
 will print a brief title. It will take a parameter 'limit' that will
 limit the number of characters printed. We can provide some
 documentation for the elemen in the docstring of the
 function.
 
  def format_element(bfo, limit="10"):
       """
       Prints a short title
 
       @param limit a limit for the number of printed characters
       """
 
       out = ""
 
       return out
 
 Note that we put a default value of 10 in the 'limit' parameter.  To
 get some value of a field, we must request the 'bfo' object. For
 example we can get the value of field 245.a (field "title"):
 
  def format_element(bfo, limit="10"):
       """
       Prints a short title
 
       @param limit a limit for the number of printed characters
       """
 
       title = bfo.field('245.a')
 
       limit = int(limit)
       if limit > len(title):
           limit = len(title)
 
       return title[:limit]
 
 As format elements are written in Python, we have decided not to give
 permission to edit elements through the web interface. Firstly for
 security reasons. Secondly because Python requires correct indentation,
 which is difficult to achieve through a web interface.
 
 You can have access to the documentation of your element through a web
 interface. This is very useful when you are writing a format template,
 to see which elements are available, what they do, which parameters they
 take, what are the default values of parameters, etc. The
 documentation is automatically extracted from format elements.
 Here follows an sample documentation generated for the element
 <BFE_TITLE />:
 
 +--------------------------------------------------------------------------------------------+
 |  TITLE                                                                                     |
 |  -----                                                                                     |
 |  <BFE_TITLE separator="..." prefix="..." suffix="..." default="..." />                  |
 |                                                                                            |
 |      Prints the title of a record.                                                         |
 |                                                                                            |
 |      Parameters:                                                                           |
 |            separator - separator between the different titles.                             |
 |            prefix - A prefix printed only if the record has a value for this element.      |
 |            suffix - A suffix printed only if the record has a value for this element.      |
 |            default - A default value printed if the record has no value for this element.  |
 |                                                                                            |
 |       See also:                                                                            |
 |            Format templates that use this element                                          |
 |            The Python code of this element                                                 |
 +--------------------------------------------------------------------------------------------+
 
 The more you provide documentation in the docstring of your elements,
 the easier it will be to write format template afterwards.
 
 Some remarks concerning format elements:
  -parameters are always string values
  -if no value is given as parameter in format the template, then the
   value of parameter is "" (emtpy string)
  -the docstring should contain a description, followed by
   "@param parameter: some description for parameter" for each
   parameter (to give description for each parameter
   in element documentation), and @see an_element.py, another_element.py
   (to link to other elements in the documentation). Similar to JavaDoc.
  -the following names cannot be used as parameters:
   "default", "prefix", "suffix" and escape. They can however always be
   used in the format template for any element.
 
 Another important remark concerns the 'escaping' of output of format
 elements. In most cases, format elements output is to be used for
 HTML/XML. Therefore special characters such as < or & have to be
 'escaped', replaced by '<' and '&'. This is why all outputs
 produced by format elements are automatically escaped by BibFormat,
 unless specified otherwise.  This means that you do not have to care
 about meta-data that would break your HTML displaying or XML export
 such as a physics formula like 'a < b'. Please also note that value
 given in 'prefix', 'suffix' and 'default' parameters are not escaped,
 such that you can safely use HTML tags for these.
 
 There are always cases where the default 'escaping' behaviour of
 BibFormat is not desired. For example when you explicitely output HTML
 text, like links: you do not want to see them escaped.  The first way
 to avoid this is to modify the call to your format element in the
 format template, by setting the default 'escape' parameter to 0:
 
  
 
 This is however inconvenient as you have to possibly need to modify a
 lot of templates. The other way of doing is to add another function to
 your format element, named 'escape':
 
  def escape_values(bfo):
      """
      Called by BibFormat in order to check if output of this element
      should be escaped.
      """
      return 0
 
 In that way all calls to your format element will produce unescaped
 output.  You will have to take care of escaping values "manually" in
 your format element code, in order to avoid non valid outputs or XSS
 vulnerabilities. There are methods to ease the escaping in your code
 described in section 1.
 Please also note that if you use this method, your element can still
 be escaped if a call to your element from a format template
 explicitely specifies to escape value using parameter 'escape'.
 
 
 5.4 Knowledge bases specifications
 
 Knowledge bases cannot be managed through configuration files.
 You can very easily add new bases and mappings using the given web GUI.
 
  -- End of file --
 
diff --git a/modules/bibformat/lib/bibformat_engine.py b/modules/bibformat/lib/bibformat_engine.py index 9fdc63828..843da3d2e 100644 --- a/modules/bibformat/lib/bibformat_engine.py +++ b/modules/bibformat/lib/bibformat_engine.py @@ -1,2057 +1,2055 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ Formats a single XML Marc record using specified format. There is no API for the engine. Instead use bibformat.py. SEE: bibformat.py, bibformat_utils.py """ __revision__ = "$Id$" import re import sys import os import inspect import traceback import zlib import cgi from invenio.config import \ CFG_PATH_PHP, \ CFG_BINDIR, \ CFG_SITE_LANG from invenio.errorlib import \ register_errors, \ get_msgs_for_code_list from invenio.bibrecord import \ create_record, \ record_get_field_instances, \ record_get_field_value, \ record_get_field_values from invenio.bibformat_xslt_engine import format from invenio.dbquery import run_sql from invenio.messages import \ language_list_long, \ wash_language, \ gettext_set_language from invenio import bibformat_dblayer from invenio.bibformat_config import \ CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION, \ CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION, \ CFG_BIBFORMAT_TEMPLATES_PATH, \ CFG_BIBFORMAT_ELEMENTS_PATH, \ CFG_BIBFORMAT_OUTPUTS_PATH, \ CFG_BIBFORMAT_ELEMENTS_IMPORT_PATH from invenio.bibformat_utils import \ record_get_xml, \ parse_tag from invenio.htmlutils import \ HTMLWasher, \ cfg_html_buffer_allowed_tag_whitelist, \ cfg_html_buffer_allowed_attribute_whitelist from invenio.webuser import collect_user_info from invenio.bibknowledge import get_kbr_values from HTMLParser import HTMLParseError if CFG_PATH_PHP: #Remove when call_old_bibformat is removed from xml.dom import minidom import tempfile # Cache for data we have already read and parsed format_templates_cache = {} format_elements_cache = {} format_outputs_cache = {} html_field = '' # String indicating that field should be # treated as HTML (and therefore no escaping of # HTML tags should occur. # Appears in some field values. washer = HTMLWasher() # Used to remove dangerous tags from HTML # sources # Regular expression for finding ... tag in format templates pattern_lang = re.compile(r''' #closing start tag (?P.*?) #anything but the next group (greedy) () #end tag ''', re.IGNORECASE | re.DOTALL | re.VERBOSE) # Builds regular expression for finding each known language in tags ln_pattern_text = r"<(" for lang in language_list_long(enabled_langs_only=False): ln_pattern_text += lang[0] +r"|" ln_pattern_text = ln_pattern_text.rstrip(r"|") ln_pattern_text += r")>(.*?)" ln_pattern = re.compile(ln_pattern_text, re.IGNORECASE | re.DOTALL) # Regular expression for finding text to be translated translation_pattern = re.compile(r'_\((?P.*?)\)_', \ re.IGNORECASE | re.DOTALL | re.VERBOSE) # Regular expression for finding tag in format templates pattern_format_template_name = re.compile(r''' #closing start tag (?P.*?) #name value. any char that is not end tag ()(\n)? #end tag ''', re.IGNORECASE | re.DOTALL | re.VERBOSE) # Regular expression for finding tag in format templates pattern_format_template_desc = re.compile(r''' #closing start tag (?P.*?) #description value. any char that is not end tag (\n)? #end tag ''', re.IGNORECASE | re.DOTALL | re.VERBOSE) # Regular expression for finding tags in format templates pattern_tag = re.compile(r''' [^/\s]+) #any char but a space or slash \s* #any number of spaces (?P(\s* #params here (?P([^=\s])*)\s* #param name: any chars that is not a white space or equality. Followed by space(s) =\s* #equality: = followed by any number of spaces (?P[\'"]) #one of the separators (?P.*?) #param value: any chars that is not a separator like previous one (?P=sep) #same separator as starting one )*) #many params \s* #any number of spaces (/)?> #end of the tag ''', re.IGNORECASE | re.DOTALL | re.VERBOSE) # Regular expression for finding params inside tags in format templates pattern_function_params = re.compile(''' (?P([^=\s])*)\s* # Param name: any chars that is not a white space or equality. Followed by space(s) =\s* # Equality: = followed by any number of spaces (?P[\'"]) # One of the separators (?P.*?) # Param value: any chars that is not a separator like previous one (?P=sep) # Same separator as starting one ''', re.VERBOSE | re.DOTALL ) # Regular expression for finding format elements "params" attributes # (defined by @param) pattern_format_element_params = re.compile(''' @param\s* # Begins with AT param keyword followed by space(s) (?P[^\s=]*):\s* # A single keyword and comma, then space(s) #(=\s*(?P[\'"]) # Equality, space(s) and then one of the separators #(?P.*?) # Default value: any chars that is not a separator like previous one #(?P=sep) # Same separator as starting one #)?\s* # Default value for param is optional. Followed by space(s) (?P.*) # Any text that is not end of line (thanks to MULTILINE parameter) ''', re.VERBOSE | re.MULTILINE) # Regular expression for finding format elements "see also" attribute # (defined by @see) pattern_format_element_seealso = re.compile('''@see:\s*(?P.*)''', re.VERBOSE | re.MULTILINE) #Regular expression for finding 2 expressions in quotes, separated by #comma (as in template("1st","2nd") ) #Used when parsing output formats ## pattern_parse_tuple_in_quotes = re.compile(''' ## (?P[\'"]) ## (?P.*) ## (?P=sep1) ## \s*,\s* ## (?P[\'"]) ## (?P.*) ## (?P=sep2) ## ''', re.VERBOSE | re.MULTILINE) def call_old_bibformat(recID, of="HD", on_the_fly=False, verbose=0): """ FIXME: REMOVE FUNCTION WHEN MIGRATION IS DONE Calls BibFormat for the record RECID in the desired output format 'of'. @param recID: record ID to format @param of: output format to be used for formatting @param on_the_fly: if False, try to return an already preformatted version of the record in the database @param verbose: verbosity Note: this functions always try to return HTML, so when bibformat returns XML with embedded HTML format inside the tag FMT $g, as is suitable for prestoring output formats, we perform un-XML-izing here in order to return HTML body only. """ out = "" res = [] if not on_the_fly: # look for formatted record existence: query = "SELECT value, last_updated FROM bibfmt WHERE "\ "id_bibrec='%s' AND format='%s'" % (recID, of) res = run_sql(query, None, 1) if res: # record 'recID' is formatted in 'of', so print it if verbose == 9: last_updated = res[0][1] out += """\n
Found preformatted output for record %i (cache updated on %s). """ % (recID, last_updated) decompress = zlib.decompress return "%s" % decompress(res[0][0]) else: # record 'recID' is not formatted in 'of', # so try to call BibFormat on the fly or use default format: if verbose == 9: out += """\n
Formatting record %i on-the-fly with old BibFormat.
""" % recID # Retrieve MARCXML # Build it on-the-fly only if 'call_old_bibformat' was called # with format=xm and on_the_fly=True xm_record = record_get_xml(recID, 'xm', on_the_fly=(on_the_fly and of == 'xm')) ## import platform ## # Some problem have been found using either popen() or os.system(). ## # Here is a temporary workaround until the issue is solved. ## if platform.python_compiler().find('Red Hat') > -1: ## # use os.system (result_code, result_path) = tempfile.mkstemp() command = "( %s/bibformat otype=%s ) > %s" % \ (CFG_BINDIR, of, result_path) (xm_code, xm_path) = tempfile.mkstemp() xm_file = open(xm_path, "w") xm_file.write(xm_record) xm_file.close() command = command + " <" + xm_path os.system(command) result_file = open(result_path,"r") bibformat_output = result_file.read() result_file.close() os.close(result_code) os.remove(result_path) os.close(xm_code) os.remove(xm_path) ## else: ## # use popen ## pipe_input, pipe_output, pipe_error = os.popen3(["%s/bibformat" % CFG_BINDIR, ## "otype=%s" % format], ## 'rw') ## pipe_input.write(xm_record) ## pipe_input.flush() ## pipe_input.close() ## bibformat_output = pipe_output.read() ## pipe_output.close() ## pipe_error.close() if bibformat_output.startswith(""): dom = minidom.parseString(bibformat_output) for e in dom.getElementsByTagName('subfield'): if e.getAttribute('code') == 'g': for t in e.childNodes: out += t.data.encode('utf-8') else: out += bibformat_output return out def format_record(recID, of, ln=CFG_SITE_LANG, verbose=0, search_pattern=None, xml_record=None, user_info=None): """ Formats a record given output format. Main entry function of bibformat engine. Returns a formatted version of the record in the specified language, search pattern, and with the specified output format. The function will define which format template must be applied. You can either specify an record ID to format, or give its xml representation. if 'xml_record' is not None, then use it instead of recID. 'user_info' allows to grant access to some functionalities on a page depending on the user's priviledges. 'user_info' is the same object as the one returned by 'webuser.collect_user_info(req)' @param recID: the ID of record to format @param of: an output format code (or short identifier for the output format) @param ln: the language to use to format the record @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, stop if error in format elements 9: errors and warnings, stop if error (debug mode )) @param search_pattern: list of strings representing the user request in web interface @param xml_record: an xml string representing the record to format @param user_info: the information of the user who will view the formatted page @return: formatted record """ if search_pattern is None: search_pattern = [] out = "" errors_ = [] # Temporary workflow (during migration of formats): # Call new BibFormat # But if format not found for new BibFormat, then call old BibFormat #Create a BibFormat Object to pass that contain record and context bfo = BibFormatObject(recID, ln, search_pattern, xml_record, user_info, of) if of.lower() != 'xm' and \ (not bfo.get_record() or len(bfo.get_record()) <= 1): # Record only has recid: do not format, excepted # for xm format return "" #Find out which format template to use based on record and output format. template = decide_format_template(bfo, of) if verbose == 9 and template is not None: out += """\n
Using %s template for record %i. """ % (template, recID) ############### FIXME: REMOVE WHEN MIGRATION IS DONE ############### path = "%s%s%s" % (CFG_BIBFORMAT_TEMPLATES_PATH, os.sep, template) if template is None or not os.access(path, os.R_OK): # template not found in new BibFormat. Call old one if verbose == 9: if template is None: out += """\n
No template found for output format %s and record %i. (Check invenio.err log file for more details) """ % (of, recID) else: out += """\n
Template %s could not be read. """ % (template) if CFG_PATH_PHP: if verbose == 9: out += """\n
Using old BibFormat for record %s. """ % recID return out + call_old_bibformat(recID, of=of, on_the_fly=True, verbose=verbose) ############################# END ################################## error = get_msgs_for_code_list([("ERR_BIBFORMAT_NO_TEMPLATE_FOUND", of)], stream='error', ln=CFG_SITE_LANG) errors_.append(error) if verbose == 0: register_errors(error, 'error') elif verbose > 5: return out + error[0][1] return out # Format with template (out_, errors) = format_with_format_template(template, bfo, verbose) errors_.extend(errors) out += out_ return out def decide_format_template(bfo, of): """ Returns the format template name that should be used for formatting given output format and BibFormatObject. Look at of rules, and take the first matching one. If no rule matches, returns None To match we ignore lettercase and spaces before and after value of rule and value of record @param bfo: a BibFormatObject @param of: the code of the output format to use """ output_format = get_output_format(of) for rule in output_format['rules']: if rule['field'].startswith('00'): # Rule uses controlfield value = bfo.control_field(rule['field']).strip() #Remove spaces else: # Rule uses datafield value = bfo.field(rule['field']).strip() #Remove spaces pattern = rule['value'].strip() #Remove spaces match_obj = re.match(pattern, value, re.IGNORECASE) if match_obj is not None and \ match_obj.start() == 0 and match_obj.end() == len(value): return rule['template'] template = output_format['default'] if template != '': return template else: return None def format_with_format_template(format_template_filename, bfo, verbose=0, format_template_code=None): """ Format a record given a format template. Also returns errors Returns a formatted version of the record represented by bfo, in the language specified in bfo, and with the specified format template. If format_template_code is provided, the template will not be loaded from format_template_filename (but format_template_filename will still be used to determine if bft or xsl transformation applies). This allows to preview format code without having to save file on disk. @param format_template_filename: the dilename of a format template @param bfo: the object containing parameters for the current formatting @param format_template_code: if not empty, use code as template instead of reading format_template_filename (used for previews) @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: tuple (formatted text, errors) """ _ = gettext_set_language(bfo.lang) def translate(match): """ Translate matching values """ word = match.group("word") translated_word = _(word) return translated_word errors_ = [] if format_template_code is not None: format_content = str(format_template_code) else: format_content = get_format_template(format_template_filename)['code'] if format_template_filename is None or \ format_template_filename.endswith("."+CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION): # .bft filtered_format = filter_languages(format_content, bfo.lang) localized_format = translation_pattern.sub(translate, filtered_format) (evaluated_format, errors) = eval_format_template_elements(localized_format, bfo, verbose) errors_ = errors else: #.xsl # Fetch MARCXML. On-the-fly xm if we are now formatting in xm xml_record = '\n' + \ record_get_xml(bfo.recID, 'xm', on_the_fly=False) # Transform MARCXML using stylesheet evaluated_format = format(xml_record, template_source=format_content) return (evaluated_format, errors_) def eval_format_template_elements(format_template, bfo, verbose=0): """ Evalutes the format elements of the given template and replace each element with its value. Also returns errors. Prepare the format template content so that we can directly replace the marc code by their value. This implies: 1) Look for special tags 2) replace special tags by their evaluation @param format_template: the format template code @param bfo: the object containing parameters for the current formatting @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: tuple (result, errors) """ errors_ = [] # First define insert_element_code(match), used in re.sub() function def insert_element_code(match): """ Analyses 'match', interpret the corresponding code, and return the result of the evaluation. Called by substitution in 'eval_format_template_elements(...)' @param match: a match object corresponding to the special tag that must be interpreted """ function_name = match.group("function_name") try: format_element = get_format_element(function_name, verbose) except Exception, e: if verbose >= 5: return '' + \ cgi.escape(str(e)).replace('\n', '
') + \ '
' if format_element is None: error = get_msgs_for_code_list([("ERR_BIBFORMAT_CANNOT_RESOLVE_ELEMENT_NAME", function_name)], stream='error', ln=CFG_SITE_LANG) errors_.append(error) if verbose >= 5: return '' + \ error[0][1]+'' else: params = {} # Look for function parameters given in format template code all_params = match.group('params') if all_params is not None: function_params_iterator = pattern_function_params.finditer(all_params) for param_match in function_params_iterator: name = param_match.group('param') value = param_match.group('value') params[name] = value # Evaluate element with params and return (Do not return errors) (result, errors) = eval_format_element(format_element, bfo, params, verbose) errors_.append(errors) return result # Substitute special tags in the format by our own text. # Special tags have the form format = pattern_tag.sub(insert_element_code, format_template) return (format, errors_) def eval_format_element(format_element, bfo, parameters={}, verbose=0): """ Returns the result of the evaluation of the given format element name, with given BibFormatObject and parameters. Also returns the errors of the evaluation. @param format_element: a format element structure as returned by get_format_element @param bfo: a BibFormatObject used for formatting @param parameters: a dict of parameters to be used for formatting. Key is parameter and value is value of parameter @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: tuple (result, errors) """ errors = [] #Load special values given as parameters prefix = parameters.get('prefix', "") suffix = parameters.get('suffix', "") default_value = parameters.get('default', "") escape = parameters.get('escape', "") output_text = '' # 3 possible cases: # a) format element file is found: we execute it # b) format element file is not found, but exist in tag table (e.g. bfe_isbn) # c) format element is totally unknown. Do nothing or report error if format_element is not None and format_element['type'] == "python": # a) We found an element with the tag name, of type "python" # Prepare a dict 'params' to pass as parameter to 'format' # function of element params = {} # Look for parameters defined in format element # Fill them with specified default values and values # given as parameters. # Also remember if the element overrides the 'escape' # parameter format_element_overrides_escape = False for param in format_element['attrs']['params']: name = param['name'] default = param['default'] params[name] = parameters.get(name, default) if name == 'escape': format_element_overrides_escape = True # Add BibFormatObject params['bfo'] = bfo # Execute function with given parameters and return result. function = format_element['code'] try: output_text = apply(function, (), params) except Exception, e: name = format_element['attrs']['name'] error = ("ERR_BIBFORMAT_EVALUATING_ELEMENT", name, str(params)) errors.append(error) if verbose == 0: register_errors(errors, 'error') elif verbose >= 5: tb = sys.exc_info()[2] error_string = get_msgs_for_code_list(error, stream='error', ln=CFG_SITE_LANG) stack = traceback.format_exception(Exception, e, tb, limit=None) output_text = ''+ \ str(error_string[0][1]) + "".join(stack) +' ' # None can be returned when evaluating function if output_text is None: output_text = "" else: output_text = str(output_text) # Escaping: # (1) By default, everything is escaped in mode 1 # (2) If evaluated element has 'escape_values()' function, use # its returned value as escape mode, and override (1) # (3) If template has a defined parameter 'escape' (in allowed # values), use it, and override (1) and (2). If this # 'escape' parameter is overriden by the format element # (defined in the 'format' function of the element), leave # the escaping job to this element # (1) escape_mode = 1 # (2) escape_function = format_element['escape_function'] if escape_function is not None: try: escape_mode = apply(escape_function, (), {'bfo': bfo}) except Exception, e: error = ("ERR_BIBFORMAT_EVALUATING_ELEMENT_ESCAPE", name) errors.append(error) if verbose == 0: register_errors(errors, 'error') elif verbose >= 5: tb = sys.exc_info()[2] error_string = get_msgs_for_code_list(error, stream='error', ln=CFG_SITE_LANG) output_text += ''+ \ str(error_string[0][1]) +' ' # (3) if escape in ['0', '1', '2', '3', '4', '5', '6']: escape_mode = int(escape) # If escape is equal to 1, then escape all # HTML reserved chars. if escape_mode > 0 and not format_element_overrides_escape: output_text = escape_field(output_text, mode=escape_mode) # Add prefix and suffix if they have been given as parameters and if # the evaluation of element is not empty if output_text.strip() != "": output_text = prefix + output_text + suffix # Add the default value if output_text is empty if output_text == "": output_text = default_value return (output_text, errors) elif format_element is not None and format_element['type'] == "field": # b) We have not found an element in files that has the tag # name. Then look for it in the table "tag" # # # # Load special values given as parameters separator = parameters.get('separator ', "") nbMax = parameters.get('nbMax', "") escape = parameters.get('escape', "1") # By default, escape here # Get the fields tags that have to be printed tags = format_element['attrs']['tags'] output_text = [] # Get values corresponding to tags for tag in tags: p_tag = parse_tag(tag) values = record_get_field_values(bfo.get_record(), p_tag[0], p_tag[1], p_tag[2], p_tag[3]) if len(values)>0 and isinstance(values[0], dict): #flatten dict to its values only values_list = map(lambda x: x.values(), values) #output_text.extend(values) for values in values_list: output_text.extend(values) else: output_text.extend(values) if nbMax != "": try: nbMax = int(nbMax) output_text = output_text[:nbMax] except: name = format_element['attrs']['name'] error = ("ERR_BIBFORMAT_NBMAX_NOT_INT", name) errors.append(error) if verbose < 5: register_errors(error, 'error') elif verbose >= 5: error_string = get_msgs_for_code_list(error, stream='error', ln=CFG_SITE_LANG) output_text = output_text.append(error_string[0][1]) # Add prefix and suffix if they have been given as parameters and if # the evaluation of element is not empty. # If evaluation is empty string, return default value if it exists. # Else return empty string if ("".join(output_text)).strip() != "": # If escape is equal to 1, then escape all # HTML reserved chars. if escape == '1': output_text = cgi.escape(separator.join(output_text)) else: output_text = separator.join(output_text) output_text = prefix + output_text + suffix else: #Return default value output_text = default_value return (output_text, errors) else: # c) Element is unknown error = get_msgs_for_code_list([("ERR_BIBFORMAT_CANNOT_RESOLVE_ELEMENT_NAME", format_element)], stream='error', ln=CFG_SITE_LANG) errors.append(error) if verbose < 5: register_errors(error, 'error') return ("", errors) elif verbose >= 5: if verbose >= 9: sys.exit(error[0][1]) return ('' + \ error[0][1]+'', errors) def filter_languages(format_template, ln='en'): """ Filters the language tags that do not correspond to the specified language. @param format_template: the format template code @param ln: the language that is NOT filtered out from the template @return: the format template with unnecessary languages filtered out """ # First define search_lang_tag(match) and clean_language_tag(match), used # in re.sub() function def search_lang_tag(match): """ Searches for the ... tag and remove inner localized tags such as , , that are not current_lang. If current_lang cannot be found inside ... , try to use 'CFG_SITE_LANG' @param match: a match object corresponding to the special tag that must be interpreted """ current_lang = ln def clean_language_tag(match): """ Return tag text content if tag language of match is output language. Called by substitution in 'filter_languages(...)' @param match: a match object corresponding to the special tag that must be interpreted """ if match.group(1) == current_lang: return match.group(2) else: return "" # End of clean_language_tag lang_tag_content = match.group("langs") # Try to find tag with current lang. If it does not exists, # then current_lang becomes CFG_SITE_LANG until the end of this # replace pattern_current_lang = re.compile(r"<("+current_lang+ \ r")\s*>(.*?)()", re.IGNORECASE | re.DOTALL) if re.search(pattern_current_lang, lang_tag_content) is None: current_lang = CFG_SITE_LANG cleaned_lang_tag = ln_pattern.sub(clean_language_tag, lang_tag_content) return cleaned_lang_tag # End of search_lang_tag filtered_format_template = pattern_lang.sub(search_lang_tag, format_template) return filtered_format_template def get_format_template(filename, with_attributes=False): """ Returns the structured content of the given formate template. if 'with_attributes' is true, returns the name and description. Else 'attrs' is not returned as key in dictionary (it might, if it has already been loaded previously) {'code':"Some template code" 'attrs': {'name': "a name", 'description': "a description"} } @param filename: the filename of an format template @param with_attributes: if True, fetch the attributes (names and description) for format' @return: strucured content of format template """ # Get from cache whenever possible global format_templates_cache if not filename.endswith("."+CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION) and \ not filename.endswith(".xsl"): return None if format_templates_cache.has_key(filename): # If we must return with attributes and template exist in # cache with attributes then return cache. # Else reload with attributes if with_attributes and \ format_templates_cache[filename].has_key('attrs'): return format_templates_cache[filename] format_template = {'code':""} try: path = "%s%s%s" % (CFG_BIBFORMAT_TEMPLATES_PATH, os.sep, filename) format_file = open(path) format_content = format_file.read() format_file.close() # Load format template code # Remove name and description if filename.endswith("."+CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION): code_and_description = pattern_format_template_name.sub("", format_content, 1) code = pattern_format_template_desc.sub("", code_and_description, 1) else: code = format_content format_template['code'] = code except Exception, e: errors = get_msgs_for_code_list([("ERR_BIBFORMAT_CANNOT_READ_TEMPLATE_FILE", filename, str(e))], stream='error', ln=CFG_SITE_LANG) register_errors(errors, 'error') # Save attributes if necessary if with_attributes: format_template['attrs'] = get_format_template_attrs(filename) # Cache and return format_templates_cache[filename] = format_template return format_template def get_format_templates(with_attributes=False): """ Returns the list of all format templates, as dictionary with filenames as keys if 'with_attributes' is true, returns the name and description. Else 'attrs' is not returned as key in each dictionary (it might, if it has already been loaded previously) [{'code':"Some template code" 'attrs': {'name': "a name", 'description': "a description"} }, ... } @param with_attributes: if True, fetch the attributes (names and description) for formats """ format_templates = {} files = os.listdir(CFG_BIBFORMAT_TEMPLATES_PATH) for filename in files: if filename.endswith("."+CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION) or \ filename.endswith(".xsl"): format_templates[filename] = get_format_template(filename, with_attributes) return format_templates def get_format_template_attrs(filename): """ Returns the attributes of the format template with given filename The attributes are {'name', 'description'} Caution: the function does not check that path exists or that the format element is valid. @param the: path to a format element """ attrs = {} attrs['name'] = "" attrs['description'] = "" try: template_file = open("%s%s%s" % (CFG_BIBFORMAT_TEMPLATES_PATH, os.sep, filename)) code = template_file.read() template_file.close() match = None if filename.endswith(".xsl"): # .xsl attrs['name'] = filename[:-4] else: # .bft match = pattern_format_template_name.search(code) if match is not None: attrs['name'] = match.group('name') else: attrs['name'] = filename match = pattern_format_template_desc.search(code) if match is not None: attrs['description'] = match.group('desc').rstrip('.') except Exception, e: errors = get_msgs_for_code_list([("ERR_BIBFORMAT_CANNOT_READ_TEMPLATE_FILE", filename, str(e))], stream='error', ln=CFG_SITE_LANG) register_errors(errors, 'error') attrs['name'] = filename return attrs def get_format_element(element_name, verbose=0, with_built_in_params=False): """ Returns the format element structured content. Return None if element cannot be loaded (file not found, not readable or invalid) The returned structure is {'attrs': {some attributes in dict. See get_format_element_attrs_from_*} 'code': the_function_code, 'type':"field" or "python" depending if element is defined in file or table, 'escape_function': the function to call to know if element output must be escaped} @param element_name: the name of the format element to load @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @param with_built_in_params: if True, load the parameters built in all elements @return: a dictionary with format element attributes """ # Get from cache whenever possible global format_elements_cache errors = [] # Resolve filename and prepare 'name' as key for the cache filename = resolve_format_element_filename(element_name) if filename is not None: name = filename.upper() else: name = element_name.upper() if format_elements_cache.has_key(name): element = format_elements_cache[name] if not with_built_in_params or \ (with_built_in_params and \ element['attrs'].has_key('builtin_params')): return element if filename is None: # Element is maybe in tag table if bibformat_dblayer.tag_exists_for_name(element_name): format_element = {'attrs': get_format_element_attrs_from_table( \ element_name, with_built_in_params), 'code':None, 'escape_function':None, 'type':"field"} # Cache and returns format_elements_cache[name] = format_element return format_element else: errors = get_msgs_for_code_list([("ERR_BIBFORMAT_FORMAT_ELEMENT_NOT_FOUND", element_name)], stream='error', ln=CFG_SITE_LANG) if verbose == 0: register_errors(errors, 'error') elif verbose >= 5: sys.stderr.write(errors[0][1]) return None else: format_element = {} module_name = filename if module_name.endswith(".py"): module_name = module_name[:-3] # Load element try: module = __import__(CFG_BIBFORMAT_ELEMENTS_IMPORT_PATH + \ "." + module_name) # Load last module in import path # For eg. load bfe_name in # invenio.bibformat_elements.bfe_name # Used to keep flexibility regarding where elements # directory is (for eg. test cases) components = CFG_BIBFORMAT_ELEMENTS_IMPORT_PATH.split(".") for comp in components[1:]: module = getattr(module, comp) except Exception, e: # We catch all exceptions here, as we just want to print # traceback in all cases tb = sys.exc_info()[2] stack = traceback.format_exception(Exception, e, tb, limit=None) errors = get_msgs_for_code_list([("ERR_BIBFORMAT_IN_FORMAT_ELEMENT", element_name,"\n" + "\n".join(stack[-2:-1]))], stream='error', ln=CFG_SITE_LANG) if verbose == 0: register_errors(errors, 'error') elif verbose >= 5: sys.stderr.write(errors[0][1]) if errors: if verbose >= 7: raise Exception, errors[0][1] return None # Load function 'format_element()' inside element try: function_format = module.__dict__[module_name].format_element format_element['code'] = function_format except AttributeError, e: # Try to load 'format()' function try: function_format = module.__dict__[module_name].format format_element['code'] = function_format except AttributeError, e: errors = get_msgs_for_code_list([("ERR_BIBFORMAT_FORMAT_ELEMENT_FORMAT_FUNCTION", element_name)], stream='error', ln=CFG_SITE_LANG) if verbose == 0: register_errors(errors, 'error') elif verbose >= 5: sys.stderr.write(errors[0][1]) if errors: if verbose >= 7: raise Exception, errors[0][1] return None # Load function 'escape_values()' inside element function_escape = getattr(module.__dict__[module_name], 'escape_values', None) format_element['escape_function'] = function_escape # Prepare, cache and return format_element['attrs'] = get_format_element_attrs_from_function( \ function_format, element_name, with_built_in_params) format_element['type'] = "python" format_elements_cache[name] = format_element return format_element def get_format_elements(with_built_in_params=False): """ Returns the list of format elements attributes as dictionary structure Elements declared in files have priority over element declared in 'tag' table The returned object has this format: {element_name1: {'attrs': {'description':..., 'seealso':... 'params':[{'name':..., 'default':..., 'description':...}, ...] 'builtin_params':[{'name':..., 'default':..., 'description':...}, ...] }, 'code': code_of_the_element }, element_name2: {...}, ...} Returns only elements that could be loaded (not error in code) @return: a dict of format elements with name as key, and a dict as attributes @param with_built_in_params: if True, load the parameters built in all elements """ format_elements = {} mappings = bibformat_dblayer.get_all_name_tag_mappings() for name in mappings: format_elements[name.upper().replace(" ", "_").strip()] = get_format_element(name, with_built_in_params=with_built_in_params) files = os.listdir(CFG_BIBFORMAT_ELEMENTS_PATH) for filename in files: filename_test = filename.upper().replace(" ", "_") if filename_test.endswith(".PY") and filename.upper() != "__INIT__.PY": if filename_test.startswith("BFE_"): filename_test = filename_test[4:] element_name = filename_test[:-3] element = get_format_element(element_name, with_built_in_params=with_built_in_params) if element is not None: format_elements[element_name] = element return format_elements def get_format_element_attrs_from_function(function, element_name, with_built_in_params=False): """ Returns the attributes of the function given as parameter. It looks for standard parameters of the function, default values and comments in the docstring. The attributes are {'description', 'seealso':['element.py', ...], 'params':{name:{'name', 'default', 'description'}, ...], name2:{}} The attributes are {'name' : "name of element" #basically the name of 'name' parameter 'description': "a string description of the element", 'seealso' : ["element_1.py", "element_2.py", ...] #a list of related elements 'params': [{'name':"param_name", #a list of parameters for this element (except 'bfo') 'default':"default value", 'description': "a description"}, ...], 'builtin_params': {name: {'name':"param_name",#the parameters builtin for all elem of this kind 'default':"default value", 'description': "a description"}, ...}, } @param function: the formatting function of a format element @param element_name: the name of the element @param with_built_in_params: if True, load the parameters built in all elements """ attrs = {} attrs['description'] = "" attrs['name'] = element_name.replace(" ", "_").upper() attrs['seealso'] = [] docstring = function.__doc__ if isinstance(docstring, str): # Look for function description in docstring #match = pattern_format_element_desc.search(docstring) description = docstring.split("@param")[0] description = description.split("@see:")[0] attrs['description'] = description.strip().rstrip('.') # Look for @see: in docstring match = pattern_format_element_seealso.search(docstring) if match is not None: elements = match.group('see').rstrip('.').split(",") for element in elements: attrs['seealso'].append(element.strip()) params = {} # Look for parameters in function definition (args, varargs, varkw, defaults) = inspect.getargspec(function) # Prepare args and defaults_list such that we can have a mapping # from args to defaults args.reverse() if defaults is not None: defaults_list = list(defaults) defaults_list.reverse() else: defaults_list = [] for arg, default in map(None, args, defaults_list): if arg == "bfo": #Don't keep this as parameter. It is hidden to users, and #exists in all elements of this kind continue param = {} param['name'] = arg if default is None: #In case no check is made inside element, we prefer to #print "" (nothing) than None in output param['default'] = "" else: param['default'] = default param['description'] = "(no description provided)" params[arg] = param if isinstance(docstring, str): # Look for AT param descriptions in docstring. # Add description to existing parameters in params dict params_iterator = pattern_format_element_params.finditer(docstring) for match in params_iterator: name = match.group('name') if params.has_key(name): params[name]['description'] = match.group('desc').rstrip('.') attrs['params'] = params.values() # Load built-in parameters if necessary if with_built_in_params: builtin_params = [] # Add 'prefix' parameter param_prefix = {} param_prefix['name'] = "prefix" param_prefix['default'] = "" param_prefix['description'] = """A prefix printed only if the record has a value for this element""" builtin_params.append(param_prefix) # Add 'suffix' parameter param_suffix = {} param_suffix['name'] = "suffix" param_suffix['default'] = "" param_suffix['description'] = """A suffix printed only if the record has a value for this element""" builtin_params.append(param_suffix) # Add 'default' parameter param_default = {} param_default['name'] = "default" param_default['default'] = "" param_default['description'] = """A default value printed if the record has no value for this element""" builtin_params.append(param_default) # Add 'escape' parameter param_escape = {} param_escape['name'] = "escape" param_escape['default'] = "" param_escape['description'] = """0 keeps value as it is. Refers to main documentation for escaping modes 1 to 6""" builtin_params.append(param_escape) attrs['builtin_params'] = builtin_params return attrs def get_format_element_attrs_from_table(element_name, with_built_in_params=False): """ Returns the attributes of the format element with given name in 'tag' table. Returns None if element_name does not exist in tag table. The attributes are {'name' : "name of element" #basically the name of 'element_name' parameter 'description': "a string description of the element", 'seealso' : [] #a list of related elements. Always empty in this case 'params': [], #a list of parameters for this element. Always empty in this case 'builtin_params': [{'name':"param_name", #the parameters builtin for all elem of this kind 'default':"default value", 'description': "a description"}, ...], 'tags':["950.1", 203.a] #the list of tags printed by this element } @param element_name: an element name in database @param element_name: the name of the element @param with_built_in_params: if True, load the parameters built in all elements """ attrs = {} tags = bibformat_dblayer.get_tags_from_name(element_name) field_label = "field" if len(tags)>1: field_label = "fields" attrs['description'] = "Prints %s %s of the record" % (field_label, ", ".join(tags)) attrs['name'] = element_name.replace(" ", "_").upper() attrs['seealso'] = [] attrs['params'] = [] attrs['tags'] = tags # Load built-in parameters if necessary if with_built_in_params: builtin_params = [] # Add 'prefix' parameter param_prefix = {} param_prefix['name'] = "prefix" param_prefix['default'] = "" param_prefix['description'] = """A prefix printed only if the record has a value for this element""" builtin_params.append(param_prefix) # Add 'suffix' parameter param_suffix = {} param_suffix['name'] = "suffix" param_suffix['default'] = "" param_suffix['description'] = """A suffix printed only if the record has a value for this element""" builtin_params.append(param_suffix) # Add 'separator' parameter param_separator = {} param_separator['name'] = "separator" param_separator['default'] = " " param_separator['description'] = """A separator between elements of the field""" builtin_params.append(param_separator) # Add 'nbMax' parameter param_nbMax = {} param_nbMax['name'] = "nbMax" param_nbMax['default'] = "" param_nbMax['description'] = """The maximum number of values to print for this element. No limit if not specified""" builtin_params.append(param_nbMax) # Add 'default' parameter param_default = {} param_default['name'] = "default" param_default['default'] = "" param_default['description'] = """A default value printed if the record has no value for this element""" builtin_params.append(param_default) # Add 'escape' parameter param_escape = {} param_escape['name'] = "escape" param_escape['default'] = "" param_escape['description'] = """If set to 1, replaces special characters '&', '<' and '>' of this element by SGML entities""" builtin_params.append(param_escape) attrs['builtin_params'] = builtin_params return attrs def get_output_format(code, with_attributes=False, verbose=0): """ Returns the structured content of the given output format If 'with_attributes' is true, also returns the names and description of the output formats, else 'attrs' is not returned in dict (it might, if it has already been loaded previously). if output format corresponding to 'code' is not found return an empty structure. See get_output_format_attrs() to learn more on the attributes {'rules': [ {'field': "980__a", 'value': "PREPRINT", 'template': "filename_a.bft", }, {...} ], 'attrs': {'names': {'generic':"a name", 'sn':{'en': "a name", 'fr':"un nom"}, 'ln':{'en':"a long name"}} 'description': "a description" 'code': "fnm1", 'content_type': "application/ms-excel", 'visibility': 1 } 'default':"filename_b.bft" } @param code: the code of an output_format @param with_attributes: if True, fetch the attributes (names and description) for format @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: strucured content of output format """ output_format = {'rules':[], 'default':""} filename = resolve_output_format_filename(code, verbose) if filename is None: errors = get_msgs_for_code_list([("ERR_BIBFORMAT_OUTPUT_FORMAT_CODE_UNKNOWN", code)], stream='error', ln=CFG_SITE_LANG) register_errors(errors, 'error') if with_attributes: #Create empty attrs if asked for attributes output_format['attrs'] = get_output_format_attrs(code, verbose) return output_format # Get from cache whenever possible global format_outputs_cache if format_outputs_cache.has_key(filename): # If was must return with attributes but cache has not # attributes, then load attributes if with_attributes and not \ format_outputs_cache[filename].has_key('attrs'): format_outputs_cache[filename]['attrs'] = get_output_format_attrs(code, verbose) return format_outputs_cache[filename] try: if with_attributes: output_format['attrs'] = get_output_format_attrs(code, verbose) path = "%s%s%s" % (CFG_BIBFORMAT_OUTPUTS_PATH, os.sep, filename ) format_file = open(path) current_tag = '' for line in format_file: line = line.strip() if line == "": # Ignore blank lines continue if line.endswith(":"): # Retrieve tag # Remove : spaces and eol at the end of line clean_line = line.rstrip(": \n\r") # The tag starts at second position current_tag = "".join(clean_line.split()[1:]).strip() elif line.find('---') != -1: words = line.split('---') template = words[-1].strip() condition = ''.join(words[:-1]) value = "" output_format['rules'].append({'field': current_tag, 'value': condition, 'template': template, }) elif line.find(':') != -1: # Default case default = line.split(':')[1].strip() output_format['default'] = default except Exception, e: errors = get_msgs_for_code_list([("ERR_BIBFORMAT_CANNOT_READ_OUTPUT_FILE", filename, str(e))], stream='error', ln=CFG_SITE_LANG) register_errors(errors, 'error') # Cache and return format_outputs_cache[filename] = output_format return output_format def get_output_format_attrs(code, verbose=0): """ Returns the attributes of an output format. The attributes contain 'code', which is the short identifier of the output format (to be given as parameter in format_record function to specify the output format), 'description', a description of the output format, 'visibility' the visibility of the format in the output format list on public pages and 'names', the localized names of the output format. If 'content_type' is specified then the search_engine will send a file with this content type and with result of formatting as content to the user. The 'names' dict always contais 'generic', 'ln' (for long name) and 'sn' (for short names) keys. 'generic' is the default name for output format. 'ln' and 'sn' contain long and short localized names of the output format. Only the languages for which a localization exist are used. {'names': {'generic':"a name", 'sn':{'en': "a name", 'fr':"un nom"}, 'ln':{'en':"a long name"}} 'description': "a description" 'code': "fnm1", 'content_type': "application/ms-excel", 'visibility': 1 } @param code: the short identifier of the format @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: strucured content of output format attributes """ if code.endswith("."+CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION): code = code[:-(len(CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION) + 1)] attrs = {'names':{'generic':"", 'ln':{}, 'sn':{}}, 'description':'', 'code':code.upper(), 'content_type':"", 'visibility':1} filename = resolve_output_format_filename(code, verbose) if filename is None: return attrs attrs['names'] = bibformat_dblayer.get_output_format_names(code) attrs['description'] = bibformat_dblayer.get_output_format_description(code) attrs['content_type'] = bibformat_dblayer.get_output_format_content_type(code) attrs['visibility'] = bibformat_dblayer.get_output_format_visibility(code) return attrs def get_output_formats(with_attributes=False): """ Returns the list of all output format, as a dictionary with their filename as key If 'with_attributes' is true, also returns the names and description of the output formats, else 'attrs' is not returned in dicts (it might, if it has already been loaded previously). See get_output_format_attrs() to learn more on the attributes {'filename_1.bfo': {'rules': [ {'field': "980__a", 'value': "PREPRINT", 'template': "filename_a.bft", }, {...} ], 'attrs': {'names': {'generic':"a name", 'sn':{'en': "a name", 'fr':"un nom"}, 'ln':{'en':"a long name"}} 'description': "a description" 'code': "fnm1" } 'default':"filename_b.bft" }, 'filename_2.bfo': {...}, ... } @return: the list of output formats """ output_formats = {} files = os.listdir(CFG_BIBFORMAT_OUTPUTS_PATH) for filename in files: if filename.endswith("."+CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION): code = "".join(filename.split(".")[:-1]) output_formats[filename] = get_output_format(code, with_attributes) return output_formats def resolve_format_element_filename(string): """ Returns the filename of element corresponding to string This is necessary since format templates code call elements by ignoring case, for eg. is the same as . It is also recommended that format elements filenames are prefixed with bfe_ . We need to look for these too. The name of the element has to start with "BFE_". @param name: a name for a format element @return: the corresponding filename, with right case """ if not string.endswith(".py"): name = string.replace(" ", "_").upper() +".PY" else: name = string.replace(" ", "_").upper() files = os.listdir(CFG_BIBFORMAT_ELEMENTS_PATH) for filename in files: test_filename = filename.replace(" ", "_").upper() if test_filename == name or \ test_filename == "BFE_" + name or \ "BFE_" + test_filename == name: return filename # No element with that name found # Do not log error, as it might be a normal execution case: # element can be in database return None def resolve_output_format_filename(code, verbose=0): """ Returns the filename of output corresponding to code This is necessary since output formats names are not case sensitive but most file systems are. @param code: the code for an output format @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: the corresponding filename, with right case, or None if not found """ #Remove non alphanumeric chars (except . and _) code = re.sub(r"[^.0-9a-zA-Z_]", "", code) if not code.endswith("."+CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION): code = re.sub(r"\W", "", code) code += "."+CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION files = os.listdir(CFG_BIBFORMAT_OUTPUTS_PATH) for filename in files: if filename.upper() == code.upper(): return filename # No output format with that name found errors = get_msgs_for_code_list([("ERR_BIBFORMAT_CANNOT_RESOLVE_OUTPUT_NAME", code)], stream='error', ln=CFG_SITE_LANG) if verbose == 0: register_errors(errors, 'error') elif verbose >= 5: sys.stderr.write(errors[0][1]) if verbose >= 9: sys.exit(errors[0][1]) return None def get_fresh_format_template_filename(name): """ Returns a new filename and name for template with given name. Used when writing a new template to a file, so that the name has no space, is unique in template directory Returns (unique_filename, modified_name) @param a: name for a format template @return: the corresponding filename, and modified name if necessary """ #name = re.sub(r"\W", "", name) #Remove non alphanumeric chars name = name.replace(" ", "_") filename = name # Remove non alphanumeric chars (except .) filename = re.sub(r"[^.0-9a-zA-Z]", "", filename) path = CFG_BIBFORMAT_TEMPLATES_PATH + os.sep + filename \ + "." + CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION index = 1 while os.path.exists(path): index += 1 filename = name + str(index) path = CFG_BIBFORMAT_TEMPLATES_PATH + os.sep + filename \ + "." + CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION if index > 1: returned_name = (name + str(index)).replace("_", " ") else: returned_name = name.replace("_", " ") return (filename + "." + CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION, returned_name) #filename.replace("_", " ")) def get_fresh_output_format_filename(code): """ Returns a new filename for output format with given code. Used when writing a new output format to a file, so that the code has no space, is unique in output format directory. The filename also need to be at most 6 chars long, as the convention is that filename == output format code (+ .extension) We return an uppercase code Returns (unique_filename, modified_code) @param code: the code of an output format @return: the corresponding filename, and modified code if necessary """ #code = re.sub(r"\W", "", code) #Remove non alphanumeric chars code = code.upper().replace(" ", "_") # Remove non alphanumeric chars (except . and _) code = re.sub(r"[^.0-9a-zA-Z_]", "", code) if len(code) > 6: code = code[:6] filename = code path = CFG_BIBFORMAT_OUTPUTS_PATH + os.sep + filename \ + "." + CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION index = 2 while os.path.exists(path): filename = code + str(index) if len(filename) > 6: filename = code[:-(len(str(index)))]+str(index) index += 1 path = CFG_BIBFORMAT_OUTPUTS_PATH + os.sep + filename \ + "." + CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION # We should not try more than 99999... Well I don't see how we # could get there.. Sanity check. if index >= 99999: errors = get_msgs_for_code_list([("ERR_BIBFORMAT_NB_OUTPUTS_LIMIT_REACHED", code)], stream='error', ln=CFG_SITE_LANG) register_errors(errors, 'error') sys.exit("Output format cannot be named as %s"%code) return (filename + "." + CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION, filename) def clear_caches(): """ Clear the caches (Output Format, Format Templates and Format Elements) """ global format_templates_cache, format_elements_cache, format_outputs_cache format_templates_cache = {} format_elements_cache = {} format_outputs_cache = {} class BibFormatObject: """ An object that encapsulates a record and associated methods, and that is given as parameter to all format elements 'format' function. The object is made specifically for a given formatting, i.e. it includes for example the language for the formatting. The object provides basic accessors to the record. For full access, one can get the record with get_record() and then use BibRecord methods on the returned object. """ # The record record = None # The language in which the formatting has to be done lang = CFG_SITE_LANG # A list of string describing the context in which the record has # to be formatted. # It represents the words of the user request in web interface search search_pattern = [] # The id of the record recID = 0 # The information about the user, as returned by # 'webuser.collect_user_info(req)' user_info = None # The format in which the record is being formatted output_format = '' req = None # DEPRECATED: use bfo.user_info instead. Used by WebJournal. def __init__(self, recID, ln=CFG_SITE_LANG, search_pattern=None, xml_record=None, user_info=None, output_format=''): """ Creates a new bibformat object, with given record. You can either specify an record ID to format, or give its xml representation. if 'xml_record' is not None, use 'xml_record' instead of recID for the record. 'user_info' allows to grant access to some functionalities on a page depending on the user's priviledges. It is a dictionary in the following form: user_info = { 'remote_ip' : '', 'remote_host' : '', 'referer' : '', 'uri' : '', 'agent' : '', - 'apache_user' : '', - 'apache_group' : [], 'uid' : -1, 'nickname' : '', 'email' : '', 'group' : [], 'guest' : '1' } @param recID: the id of a record @param ln: the language in which the record has to be formatted @param search_pattern: list of string representing the request used by the user in web interface @param xml_record: a xml string of the record to format @param user_info: the information of the user who will view the formatted page @param output_format: the output_format used for formatting this record """ if xml_record is not None: # If record is given as parameter self.record = create_record(xml_record)[0] recID = record_get_field_value(self.record, "001") self.lang = wash_language(ln) if search_pattern is None: search_pattern = [] self.search_pattern = search_pattern self.recID = recID self.output_format = output_format self.user_info = user_info if self.user_info is None: self.user_info = collect_user_info(None) def get_record(self): """ Returns the record structure of this BibFormatObject instance @return: the record structure as defined by BibRecord library """ from invenio.search_engine import get_record # Create record if necessary if self.record is None: # on-the-fly creation if current output is xm self.record = get_record(self.recID) return self.record def control_field(self, tag, escape=0): """ Returns the value of control field given by tag in record @param tag: the marc code of a field @param escape: 1 if returned value should be escaped. Else 0. @return: value of field tag in record """ if self.get_record() is None: #Case where BibRecord could not parse object return '' p_tag = parse_tag(tag) field_value = record_get_field_value(self.get_record(), p_tag[0], p_tag[1], p_tag[2], p_tag[3]) if escape == 0: return field_value else: return escape_field(field_value, escape) def field(self, tag, escape=0): """ Returns the value of the field corresponding to tag in the current record. If the value does not exist, return empty string. Else returns the same as bfo.fields(..)[0] (see docstring below). 'escape' parameter allows to escape special characters of the field. The value of escape can be: 0 - no escaping 1 - escape all HTML characters 2 - remove unsafe HTML tags (Eg. keep
) 3 - Mix of mode 1 and 2. If value of field starts with , then use mode 2. Else use mode 1. 4 - Remove all HTML tags 5 - Same as 2, with more tags allowed (like ) 6 - Same as 3, with more tags allowed (like ) @param tag: the marc code of a field @param escape: 1 if returned value should be escaped. Else 0. (see above for other modes) @return: value of field tag in record """ list_of_fields = self.fields(tag) if len(list_of_fields) > 0: # Escaping below if escape == 0: return list_of_fields[0] else: return escape_field(list_of_fields[0], escape) else: return "" def fields(self, tag, escape=0, repeatable_subfields_p=False): """ Returns the list of values corresonding to "tag". If tag has an undefined subcode (such as 999C5), the function returns a list of dictionaries, whoose keys are the subcodes and the values are the values of tag.subcode. If the tag has a subcode, simply returns list of values corresponding to tag. Eg. for given MARC: 999C5 $a value_1a $b value_1b 999C5 $b value_2b 999C5 $b value_3b $b value_3b_bis >> bfo.fields('999C5b') >> ['value_1b', 'value_2b', 'value_3b', 'value_3b_bis'] >> bfo.fields('999C5') >> [{'a':'value_1a', 'b':'value_1b'}, {'b':'value_2b'}, {'b':'value_3b'}] By default the function returns only one value for each subfield (that is it considers that repeatable subfields are not allowed). It is why in the above example 'value3b_bis' is not shown for bfo.fields('999C5'). (Note that it is not defined which of value_3b or value_3b_bis is returned). This is to simplify the use of the function, as most of the time subfields are not repeatable (in that way we get a string instead of a list). You can allow repeatable subfields by setting 'repeatable_subfields_p' parameter to True. In this mode, the above example would return: >> bfo.fields('999C5b', repeatable_subfields_p=True) >> ['value_1b', 'value_2b', 'value_3b'] >> bfo.fields('999C5', repeatable_subfields_p=True) >> [{'a':['value_1a'], 'b':['value_1b']}, {'b':['value_2b']}, {'b':['value_3b', 'value3b_bis']}] NOTICE THAT THE RETURNED STRUCTURE IS DIFFERENT. Also note that whatever the value of 'repeatable_subfields_p' is, bfo.fields('999C5b') always show all fields, even repeatable ones. This is because the parameter has no impact on the returned structure (it is always a list). 'escape' parameter allows to escape special characters of the fields. The value of escape can be: 0 - no escaping 1 - escape all HTML characters 2 - remove unsafe HTML tags (Eg. keep
) 3 - Mix of mode 1 and 2. If value of field starts with , then use mode 2. Else use mode 1. 4 - Remove all HTML tags 5 - Same as 2, with more tags allowed (like ) 6 - Same as 3, with more tags allowed (like ) @param tag: the marc code of a field @param escape: 1 if returned values should be escaped. Else 0. @repeatable_subfields_p if True, returns the list of subfields in the dictionary @return: values of field tag in record """ if self.get_record() is None: # Case where BibRecord could not parse object return [] p_tag = parse_tag(tag) if p_tag[3] != "": # Subcode has been defined. Simply returns list of values values = record_get_field_values(self.get_record(), p_tag[0], p_tag[1], p_tag[2], p_tag[3]) if escape == 0: return values else: return [escape_field(value, escape) for value in values] else: # Subcode is undefined. Returns list of dicts. # However it might be the case of a control field. instances = record_get_field_instances(self.get_record(), p_tag[0], p_tag[1], p_tag[2]) if repeatable_subfields_p: list_of_instances = [] for instance in instances: instance_dict = {} for subfield in instance[0]: if not instance_dict.has_key(subfield[0]): instance_dict[subfield[0]] = [] if escape == 0: instance_dict[subfield[0]].append(subfield[1]) else: instance_dict[subfield[0]].append(escape_field(subfield[1], escape)) list_of_instances.append(instance_dict) return list_of_instances else: if escape == 0: return [dict(instance[0]) for instance in instances] else: return [dict([ (subfield[0], escape_field(subfield[1], escape)) \ for subfield in instance[0] ]) \ for instance in instances] def kb(self, kb, string, default=""): """ Returns the value of the "string" in the knowledge base "kb". If kb does not exist or string does not exist in kb, returns 'default' string or empty string if not specified. @param kb: a knowledge base name @param string: the string we want to translate @param default: a default value returned if 'string' not found in 'kb' """ if string is None: return default val = get_kbr_values(kb, searchkey=string, searchtype='e') try: return val[0][0] except: return default def escape_field(value, mode=0): """ Utility function used to escape the value of a field in given mode. - mode 0: no escaping - mode 1: escaping all HTML/XML characters (escaped chars are shown as escaped) - mode 2: escaping unsafe HTML tags to avoid XSS, but keep basic one (such as
) Escaped tags are removed. - mode 3: mix of mode 1 and mode 2. If field_value starts with , then use mode 2. Else use mode 1. - mode 4: escaping all HTML/XML tags (escaped tags are removed) - mode 5: same as 2, but allows more tags, like - mode 6: same as 3, but allows more tags, like """ if mode == 1: return cgi.escape(value) elif mode in [2, 5]: allowed_attribute_whitelist = cfg_html_buffer_allowed_attribute_whitelist allowed_tag_whitelist = cfg_html_buffer_allowed_tag_whitelist + \ ('class',) if mode == 5: allowed_attribute_whitelist += ('src', 'alt', 'width', 'height', 'style', 'summary', 'border', 'cellspacing', 'cellpadding') allowed_tag_whitelist += ('img', 'table', 'td', 'tr', 'th', 'span', 'caption') try: return washer.wash(value, allowed_attribute_whitelist=\ allowed_attribute_whitelist, allowed_tag_whitelist= \ allowed_tag_whitelist ) except HTMLParseError: # Parsing failed return cgi.escape(value) elif mode in [3, 6]: if value.lstrip(' \n').startswith(html_field): allowed_attribute_whitelist = cfg_html_buffer_allowed_attribute_whitelist allowed_tag_whitelist = cfg_html_buffer_allowed_tag_whitelist + \ ('class',) if mode == 6: allowed_attribute_whitelist += ('src', 'alt', 'width', 'height', 'style', 'summary', 'border', 'cellspacing', 'cellpadding') allowed_tag_whitelist += ('img', 'table', 'td', 'tr', 'th', 'span', 'caption') try: return washer.wash(value, allowed_attribute_whitelist=\ allowed_attribute_whitelist, allowed_tag_whitelist=\ allowed_tag_whitelist ) except HTMLParseError: # Parsing failed return cgi.escape(value) else: return cgi.escape(value) elif mode == 4: try: return washer.wash(value, allowed_attribute_whitelist=[], allowed_tag_whitelist=[] ) except HTMLParseError: # Parsing failed return cgi.escape(value) else: return value def bf_profile(): """ Runs a benchmark """ for i in range(1, 51): format_record(i, "HD", ln=CFG_SITE_LANG, verbose=9, search_pattern=[]) return if __name__ == "__main__": import profile import pstats #bf_profile() profile.run('bf_profile()', "bibformat_profile") p = pstats.Stats("bibformat_profile") p.strip_dirs().sort_stats("cumulative").print_stats() diff --git a/modules/bibsched/lib/bibtask.py b/modules/bibsched/lib/bibtask.py index 86db3dda4..7e1bc64b2 100644 --- a/modules/bibsched/lib/bibtask.py +++ b/modules/bibsched/lib/bibtask.py @@ -1,986 +1,986 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Invenio Bibliographic Task Class. BibTask class. A BibTask is an executable under CFG_BINDIR, whose name is stored in bibtask_config.CFG_BIBTASK_VALID_TASKS. A valid task must call the task_init function with the proper parameters. Generic task related parameters (user, sleeptime, runtime, task_id, task_name verbose) go to _TASK_PARAMS global dictionary accessible through task_get_task_param. Option specific to the particular BibTask go to _OPTIONS global dictionary and are accessible via task_get_option/task_set_option. In order to log something properly, just use write_message(s) with the desired verbose level. task_update_status and task_update_progress can be used to update the status of the task (DONE, FAILED, DONE WITH ERRORS...) and it's progress (1 out 100..) within the bibsched monitor. It is possible to enqueue a BibTask via API call by means of task_low_level_submission. """ __revision__ = "$Id$" import getopt import getpass import marshal import os import pwd import re import signal import sys import time import datetime import traceback import logging import logging.handlers from invenio.dbquery import run_sql, _db_login from invenio.access_control_engine import acc_authorize_action from invenio.config import CFG_PREFIX, CFG_BINDIR, CFG_LOGDIR, \ CFG_BIBSCHED_PROCESS_USER, CFG_TMPDIR from invenio.errorlib import register_exception from invenio.access_control_config import CFG_EXTERNAL_AUTH_USING_SSO, \ CFG_EXTERNAL_AUTHENTICATION from invenio.webuser import get_user_preferences, get_email from invenio.bibtask_config import CFG_BIBTASK_VALID_TASKS, \ CFG_BIBTASK_DEFAULT_TASK_SETTINGS # Global _TASK_PARAMS dictionary. _TASK_PARAMS = { 'version': '', 'task_stop_helper_fnc': None, 'task_name': os.path.basename(sys.argv[0]), 'task_specific_name': '', 'user': '', # If the task is not initialized (usually a developer debugging # a single method), output all messages. 'verbose': 9, 'sleeptime': '', 'runtime': time.strftime("%Y-%m-%d %H:%M:%S"), 'priority': 0, 'runtime_limit': None, 'profile': [], } # Global _OPTIONS dictionary. _OPTIONS = {} # Which tasks don't need to ask the user for authorization? CFG_VALID_PROCESSES_NO_AUTH_NEEDED = ("bibupload", ) CFG_TASK_IS_NOT_A_DEAMON = ("bibupload", ) def fix_argv_paths(paths, argv=None): """Given the argv vector of cli parameters, and a list of path that can be relative and may have been specified within argv, it substitute all the occurencies of these paths in argv. argv is changed in place and returned. """ if argv is None: argv = sys.argv for path in paths: for count in xrange(len(argv)): if path == argv[count]: argv[count] = os.path.abspath(path) return argv def task_low_level_submission(name, user, *argv): """Let special lowlevel enqueuing of a task on the bibsche queue. @param name: is the name of the bibtask. It must be a valid executable under C{CFG_BINDIR}. @type name: string @param user: is a string that will appear as the "user" submitting the task. Since task are submitted via API it make sense to set the user to the name of the module/function that called task_low_level_submission. @type user: string @param argv: are all the additional CLI parameters that would have been passed on the CLI (one parameter per variable). e.g.: >>> task_low_level_submission('bibupload', 'admin', '-a', '/tmp/z.xml') @type: strings @return: the task identifier when the task is correctly enqueued. @rtype: int @note: use absolute paths in argv """ def get_priority(argv): """Try to get the priority by analysing the arguments.""" priority = 0 argv = list(argv) while True: try: opts, args = getopt.gnu_getopt(argv, 'P:', ['priority=']) except getopt.GetoptError, err: ## We remove one by one all the non recognized parameters if len(err.opt) > 1: argv = [arg for arg in argv if arg != '--%s' % err.opt and not arg.startswith('--%s=' % err.opt)] else: argv = [arg for arg in argv if not arg.startswith('-%s' % err.opt)] else: break for opt in opts: if opt[0] in ('-P', '--priority'): try: priority = int(opt[1]) except ValueError: pass return priority def get_special_name(argv): """Try to get the special name by analysing the arguments.""" special_name = '' argv = list(argv) while True: try: opts, args = getopt.gnu_getopt(argv, 'N:', ['name=']) except getopt.GetoptError, err: ## We remove one by one all the non recognized parameters if len(err.opt) > 1: argv = [arg for arg in argv if arg != '--%s' % err.opt and not arg.startswith('--%s=' % err.opt)] else: argv = [arg for arg in argv if not arg.startswith('-%s' % err.opt)] else: break for opt in opts: if opt[0] in ('-N', '--name'): special_name = opt[1] return special_name task_id = None try: if not name in CFG_BIBTASK_VALID_TASKS: raise StandardError('%s is not a valid task name' % name) priority = get_priority(argv) special_name = get_special_name(argv) argv = tuple([os.path.join(CFG_BINDIR, name)] + list(argv)) if special_name: name = '%s:%s' % (name, special_name) ## submit task: task_id = run_sql("""INSERT INTO schTASK (proc,user, runtime,sleeptime,status,progress,arguments,priority) VALUES (%s,%s,NOW(),'','WAITING','',%s,%s)""", (name, user, marshal.dumps(argv), priority)) except Exception: register_exception(alert_admin=True) if task_id: run_sql("""DELETE FROM schTASK WHERE id=%s""", (task_id, )) raise return task_id def setup_loggers(task_id=None): """Sets up the logging system.""" logger = logging.getLogger() for handler in logger.handlers: ## Let's clean the handlers in case some piece of code has already ## fired any write_message, i.e. any call to debug, info, etc. ## which triggered a call to logging.basicConfig() logger.removeHandler(handler) formatter = logging.Formatter('%(asctime)s --> %(message)s', '%Y-%m-%d %H:%M:%S') if task_id is not None: err_logger = logging.handlers.RotatingFileHandler(os.path.join(CFG_LOGDIR, 'bibsched_task_%d.err' % _TASK_PARAMS['task_id']), 'a', 1*1024*1024, 10) log_logger = logging.handlers.RotatingFileHandler(os.path.join(CFG_LOGDIR, 'bibsched_task_%d.log' % _TASK_PARAMS['task_id']), 'a', 1*1024*1024, 10) log_logger.setFormatter(formatter) log_logger.setLevel(logging.DEBUG) err_logger.setFormatter(formatter) err_logger.setLevel(logging.WARNING) logger.addHandler(err_logger) logger.addHandler(log_logger) stdout_logger = logging.StreamHandler(sys.stdout) stdout_logger.setFormatter(formatter) stdout_logger.setLevel(logging.DEBUG) stderr_logger = logging.StreamHandler(sys.stderr) stderr_logger.setFormatter(formatter) stderr_logger.setLevel(logging.WARNING) logger.addHandler(stderr_logger) logger.addHandler(stdout_logger) logger.setLevel(logging.INFO) return logger def task_init( authorization_action="", authorization_msg="", description="", help_specific_usage="", version=__revision__, specific_params=("", []), task_stop_helper_fnc=None, task_submit_elaborate_specific_parameter_fnc=None, task_submit_check_options_fnc=None, task_run_fnc=None): """ Initialize a BibTask. @param authorization_action: is the name of the authorization action connected with this task; @param authorization_msg: is the header printed when asking for an authorization password; @param description: is the generic description printed in the usage page; @param help_specific_usage: is the specific parameter help @param task_stop_fnc: is a function that will be called whenever the task is stopped @param task_submit_elaborate_specific_parameter_fnc: will be called passing a key and a value, for parsing specific cli parameters. Must return True if it has recognized the parameter. Must eventually update the options with bibtask_set_option; @param task_submit_check_options: must check the validity of options (via bibtask_get_option) once all the options where parsed; @param task_run_fnc: will be called as the main core function. Must return False in case of errors. """ global _TASK_PARAMS, _OPTIONS _TASK_PARAMS = { "version" : version, "task_stop_helper_fnc" : task_stop_helper_fnc, "task_name" : os.path.basename(sys.argv[0]), "task_specific_name" : '', "user" : '', "verbose" : 1, "sleeptime" : '', "runtime" : time.strftime("%Y-%m-%d %H:%M:%S"), "priority" : 0, "runtime_limit" : None, "profile" : [], } to_be_submitted = True if len(sys.argv) == 2 and sys.argv[1].isdigit(): _TASK_PARAMS['task_id'] = int(sys.argv[1]) argv = _task_get_options(_TASK_PARAMS['task_id'], _TASK_PARAMS['task_name']) to_be_submitted = False else: argv = sys.argv setup_loggers(_TASK_PARAMS.get('task_id')) if type(argv) is dict: # FIXME: REMOVE AFTER MAJOR RELEASE 1.0 # This is needed for old task submitted before CLI parameters # where stored in DB and _OPTIONS dictionary was stored instead. _OPTIONS = argv else: try: _task_build_params(_TASK_PARAMS['task_name'], argv, description, help_specific_usage, version, specific_params, task_submit_elaborate_specific_parameter_fnc, task_submit_check_options_fnc) except SystemExit: raise except Exception, e: register_exception(alert_admin=True) write_message("Error in parsing the parameters: %s." % e, sys.stderr) write_message("Exiting.", sys.stderr) if not to_be_submitted: task_update_status("ERROR") raise write_message('argv=%s' % (argv, ), verbose=9) write_message('_OPTIONS=%s' % (_OPTIONS, ), verbose=9) write_message('_TASK_PARAMS=%s' % (_TASK_PARAMS, ), verbose=9) if to_be_submitted: _task_submit(argv, authorization_action, authorization_msg) else: try: if task_get_task_param('profile'): try: from cStringIO import StringIO import pstats filename = os.path.join(CFG_TMPDIR, 'bibsched_task_%s.pyprof' % _TASK_PARAMS['task_id']) existing_sorts = pstats.Stats.sort_arg_dict_default.keys() required_sorts = [] profile_dump = [] for sort in task_get_task_param('profile'): if sort not in existing_sorts: sort = 'cumulative' if sort not in required_sorts: required_sorts.append(sort) if sys.hexversion < 0x02050000: import hotshot import hotshot.stats pr = hotshot.Profile(filename) ret = pr.runcall(_task_run, task_run_fnc) for sort_type in required_sorts: tmp_out = sys.stdout sys.stdout = StringIO() hotshot.stats.load(filename).strip_dirs().sort_stats(sort_type).print_stats() # pylint: disable=E1103 # This is a hack. sys.stdout is a StringIO in this case. profile_dump.append(sys.stdout.getvalue()) # pylint: enable=E1103 sys.stdout = tmp_out else: import cProfile pr = cProfile.Profile() ret = pr.runcall(_task_run, task_run_fnc) pr.dump_stats(filename) for sort_type in required_sorts: strstream = StringIO() pstats.Stats(filename, stream=strstream).strip_dirs().sort_stats(sort_type).print_stats() profile_dump.append(strstream.getvalue()) profile_dump = '\n'.join(profile_dump) profile_dump += '\nYou can use profile=%s' % existing_sorts open(os.path.join(CFG_LOGDIR, 'bibsched_task_%d.log' % _TASK_PARAMS['task_id']), 'a').write("%s" % profile_dump) os.remove(filename) except ImportError: ret = _task_run(task_run_fnc) write_message("ERROR: The Python Profiler is not installed!", stream=sys.stderr) else: ret = _task_run(task_run_fnc) if not ret: write_message("Error occurred. Exiting.", sys.stderr) except Exception, e: register_exception(alert_admin=True) write_message("Unexpected error occurred: %s." % e, sys.stderr) write_message("Traceback is:", sys.stderr) write_messages(''.join(traceback.format_tb(sys.exc_info()[2])), sys.stderr) write_message("Exiting.", sys.stderr) task_update_status("ERROR") logging.shutdown() def _task_build_params( task_name, argv, description="", help_specific_usage="", version=__revision__, specific_params=("", []), task_submit_elaborate_specific_parameter_fnc=None, task_submit_check_options_fnc=None): """ Build the BibTask params. @param argv: a list of string as in sys.argv @param description: is the generic description printed in the usage page; @param help_specific_usage: is the specific parameter help @param task_submit_elaborate_specific_parameter_fnc: will be called passing a key and a value, for parsing specific cli parameters. Must return True if it has recognized the parameter. Must eventually update the options with bibtask_set_option; @param task_submit_check_options: must check the validity of options (via bibtask_get_option) once all the options where parsed; """ global _OPTIONS _OPTIONS = {} if task_name in CFG_BIBTASK_DEFAULT_TASK_SETTINGS: _OPTIONS.update(CFG_BIBTASK_DEFAULT_TASK_SETTINGS[task_name]) # set user-defined options: try: (short_params, long_params) = specific_params opts, args = getopt.gnu_getopt(argv[1:], "hVv:u:s:t:P:N:L:" + short_params, [ "help", "version", "verbose=", "user=", "sleep=", "runtime=", "priority=", "name=", "limit=", "profile=" ] + long_params) except getopt.GetoptError, err: _usage(1, err, help_specific_usage=help_specific_usage, description=description) try: for opt in opts: if opt[0] in ("-h", "--help"): _usage(0, help_specific_usage=help_specific_usage, description=description) elif opt[0] in ("-V", "--version"): print _TASK_PARAMS["version"] sys.exit(0) elif opt[0] in ("-u", "--user"): _TASK_PARAMS["user"] = opt[1] elif opt[0] in ("-v", "--verbose"): _TASK_PARAMS["verbose"] = int(opt[1]) elif opt[0] in ("-s", "--sleeptime"): if task_name not in CFG_TASK_IS_NOT_A_DEAMON: get_datetime(opt[1]) # see if it is a valid shift _TASK_PARAMS["sleeptime"] = opt[1] elif opt[0] in ("-t", "--runtime"): _TASK_PARAMS["runtime"] = get_datetime(opt[1]) elif opt[0] in ("-P", "--priority"): _TASK_PARAMS["priority"] = int(opt[1]) elif opt[0] in ("-N", "--name"): _TASK_PARAMS["task_specific_name"] = opt[1] elif opt[0] in ("-L", "--limit"): _TASK_PARAMS["runtime_limit"] = parse_runtime_limit(opt[1]) elif opt[0] in ("--profile", ): _TASK_PARAMS["profile"] += opt[1].split(',') elif not callable(task_submit_elaborate_specific_parameter_fnc) or \ not task_submit_elaborate_specific_parameter_fnc(opt[0], opt[1], opts, args): _usage(1, help_specific_usage=help_specific_usage, description=description) except StandardError, e: _usage(e, help_specific_usage=help_specific_usage, description=description) if callable(task_submit_check_options_fnc): if not task_submit_check_options_fnc(): _usage(1, help_specific_usage=help_specific_usage, description=description) def task_set_option(key, value): """Set an value to key in the option dictionary of the task""" global _OPTIONS try: _OPTIONS[key] = value except NameError: _OPTIONS = {key : value} def task_get_option(key, default=None): """Returns the value corresponding to key in the option dictionary of the task""" try: return _OPTIONS.get(key, default) except NameError: return default def task_has_option(key): """Map the has_key query to _OPTIONS""" try: return _OPTIONS.has_key(key) except NameError: return False def task_get_task_param(key, default=None): """Returns the value corresponding to the particular task param""" try: return _TASK_PARAMS.get(key, default) except NameError: return default def task_set_task_param(key, value): """Set the value corresponding to the particular task param""" global _TASK_PARAMS try: _TASK_PARAMS[key] = value except NameError: _TASK_PARAMS = {key : value} def task_update_progress(msg): """Updates progress information in the BibSched task table.""" write_message("Updating task progress to %s." % msg, verbose=9) return run_sql("UPDATE schTASK SET progress=%s where id=%s", (msg, _TASK_PARAMS["task_id"])) def task_update_status(val): """Updates status information in the BibSched task table.""" write_message("Updating task status to %s." % val, verbose=9) return run_sql("UPDATE schTASK SET status=%s where id=%s", (val, _TASK_PARAMS["task_id"])) def task_read_status(): """Read status information in the BibSched task table.""" res = run_sql("SELECT status FROM schTASK where id=%s", (_TASK_PARAMS['task_id'],), 1) try: out = res[0][0] except: out = 'UNKNOWN' return out def write_messages(msgs, stream=sys.stdout, verbose=1): """Write many messages through write_message""" for msg in msgs.split('\n'): write_message(msg, stream, verbose) def write_message(msg, stream=sys.stdout, verbose=1): """Write message and flush output stream (may be sys.stdout or sys.stderr). Useful for debugging stuff.""" if msg and _TASK_PARAMS['verbose'] >= verbose: if stream == sys.stdout: logging.info(msg) elif stream == sys.stderr: logging.error(msg) else: sys.stderr.write("Unknown stream %s. [must be sys.stdout or sys.stderr]\n" % stream) else: logging.debug(msg) _RE_SHIFT = re.compile("([-\+]{0,1})([\d]+)([dhms])") def get_datetime(var, format_string="%Y-%m-%d %H:%M:%S"): """Returns a date string according to the format string. It can handle normal date strings and shifts with respect to now.""" date = time.time() factors = {"d":24*3600, "h":3600, "m":60, "s":1} m = _RE_SHIFT.match(var) if m: sign = m.groups()[0] == "-" and -1 or 1 factor = factors[m.groups()[2]] value = float(m.groups()[1]) date = time.localtime(date + sign * factor * value) date = time.strftime(format_string, date) else: date = time.strptime(var, format_string) date = time.strftime(format_string, date) return date _RE_RUNTIMELIMIT_FULL = re.compile(r"(?P[a-z]+)?\s*((?P\d\d?(:\d\d?)?)(-(?P\d\d?(:\d\d?)?))?)?", re.I) _RE_RUNTIMELIMIT_HOUR = re.compile(r'(?P\d\d?)(:(?P\d\d?))?') def parse_runtime_limit(value): """ Parsing CLI option for runtime limit, supplied as VALUE. Value could be something like: Sunday 23:00-05:00, the format being [Wee[kday]] [hh[:mm][-hh[:mm]]]. The function will return two valid time ranges. The first could be in the past, containing the present or in the future. The second is always in the future. """ def extract_time(value): value = _RE_RUNTIMELIMIT_HOUR.search(value).groupdict() hour = int(value['hour']) % 24 minutes = (value['minutes'] is not None and int(value['minutes']) or 0) % 60 return hour * 3600 + minutes * 60 def extract_weekday(value): try: return { 'mon' : 0, 'tue' : 1, 'wed' : 2, 'thu' : 3, 'fri' : 4, 'sat' : 5, 'sun' : 6, }[value[:3].lower()] except KeyError: raise ValueError, "%s is not a good weekday name." % value today = datetime.datetime.today() try: g = _RE_RUNTIMELIMIT_FULL.search(value) if not g: raise ValueError pieces = g.groupdict() today_weekday = today.isoweekday() - 1 if pieces['weekday'] is None: ## No weekday specified. So either today or tomorrow first_occasion_day = 0 next_occasion_day = 24 * 3600 else: ## Weekday specified. So either this week or next weekday = extract_weekday(pieces['weekday']) first_occasion_day = -((today_weekday - weekday) % 7) * 24 * 3600 next_occasion_day = first_occasion_day + 7 * 24 * 3600 if pieces['begin'] is None: pieces['begin'] = '00:00' if pieces['end'] is None: pieces['end'] = '00:00' beginning_time = extract_time(pieces['begin']) ending_time = extract_time(pieces['end']) if beginning_time >= ending_time: ## end < begin we add a 24-hours watch tour. ending_time += 24 * 3600 reference_time = time.mktime(datetime.datetime(today.year, today.month, today.day).timetuple()) first_range = ( reference_time + first_occasion_day + beginning_time, reference_time + first_occasion_day + ending_time ) second_range = ( reference_time + next_occasion_day + beginning_time, reference_time + next_occasion_day + ending_time ) return first_range, second_range except ValueError: raise except: raise ValueError, '"%s" does not seem to be correct format for parse_runtime_limit() [Wee[kday]] [hh[:mm][-hh[:mm]]]).' % value def task_sleep_now_if_required(can_stop_too=False): """This function should be called during safe state of BibTask, e.g. after flushing caches or outside of run_sql calls. """ status = task_read_status() write_message('Entering task_sleep_now_if_required with status=%s' % status, verbose=9) if status == 'ABOUT TO SLEEP': write_message("sleeping...") task_update_status("SLEEPING") signal.signal(signal.SIGTSTP, _task_sig_dumb) os.kill(os.getpid(), signal.SIGSTOP) time.sleep(1) task_update_status("CONTINUING") write_message("... continuing...") signal.signal(signal.SIGTSTP, _task_sig_sleep) elif status == 'ABOUT TO STOP' and can_stop_too: write_message("stopped") task_update_status("STOPPED") sys.exit(0) runtime_limit = task_get_option("limit") if runtime_limit is not None: if not (runtime_limit[0] <= time.time() <= runtime_limit[1]): if can_stop_too: write_message("stopped (outside runtime limit)") task_update_status("STOPPED") sys.exit(0) def authenticate(user, authorization_action, authorization_msg=""): """Authenticate the user against the user database. Check for its password, if it exists. Check for authorization_action access rights. Return user name upon authorization success, do system exit upon authorization failure. """ # With SSO it's impossible to check for pwd if CFG_EXTERNAL_AUTH_USING_SSO or os.path.basename(sys.argv[0]) in CFG_VALID_PROCESSES_NO_AUTH_NEEDED: return user if authorization_msg: print authorization_msg print "=" * len(authorization_msg) if user == "": print >> sys.stdout, "\rUsername: ", try: user = sys.stdin.readline().lower().strip() except EOFError: sys.stderr.write("\n") sys.exit(1) except KeyboardInterrupt: sys.stderr.write("\n") sys.exit(1) else: print >> sys.stdout, "\rUsername:", user ## first check user: # p_un passed may be an email or a nickname: res = run_sql("select id from user where email=%s", (user,), 1) + \ run_sql("select id from user where nickname=%s", (user,), 1) if not res: print "Sorry, %s does not exist." % user sys.exit(1) else: uid = res[0][0] ok = False login_method = get_user_preferences(uid)['login_method'] - if not CFG_EXTERNAL_AUTHENTICATION[login_method][0]: + if not CFG_EXTERNAL_AUTHENTICATION[login_method]: #Local authentication, let's see if we want passwords. res = run_sql("select id from user where id=%s " "and password=AES_ENCRYPT(email,'')", (uid,), 1) if res: ok = True if not ok: try: password_entered = getpass.getpass() except EOFError: sys.stderr.write("\n") sys.exit(1) except KeyboardInterrupt: sys.stderr.write("\n") sys.exit(1) - if not CFG_EXTERNAL_AUTHENTICATION[login_method][0]: + if not CFG_EXTERNAL_AUTHENTICATION[login_method]: res = run_sql("select id from user where id=%s " "and password=AES_ENCRYPT(email, %s)", (uid, password_entered), 1) if res: ok = True else: - if CFG_EXTERNAL_AUTHENTICATION[login_method][0].auth_user(get_email(uid), password_entered): + if CFG_EXTERNAL_AUTHENTICATION[login_method].auth_user(get_email(uid), password_entered): ok = True if not ok: print "Sorry, wrong credentials for %s." % user sys.exit(1) else: ## secondly check authorization for the authorization_action: (auth_code, auth_message) = acc_authorize_action(uid, authorization_action) if auth_code != 0: print auth_message sys.exit(1) return user def _task_submit(argv, authorization_action, authorization_msg): """Submits task to the BibSched task queue. This is what people will be invoking via command line.""" ## check as whom we want to submit? check_running_process_user() ## sanity check: remove eventual "task" option: ## authenticate user: _TASK_PARAMS['user'] = authenticate(_TASK_PARAMS["user"], authorization_action, authorization_msg) ## submit task: if _TASK_PARAMS['task_specific_name']: task_name = '%s:%s' % (_TASK_PARAMS['task_name'], _TASK_PARAMS['task_specific_name']) else: task_name = _TASK_PARAMS['task_name'] write_message("storing task options %s\n" % argv, verbose=9) _TASK_PARAMS['task_id'] = run_sql("""INSERT INTO schTASK (proc,user, runtime,sleeptime,status,progress,arguments,priority) VALUES (%s,%s,%s,%s,'WAITING','',%s, %s)""", (task_name, _TASK_PARAMS['user'], _TASK_PARAMS["runtime"], _TASK_PARAMS["sleeptime"], marshal.dumps(argv), _TASK_PARAMS['priority'])) ## update task number: write_message("Task #%d submitted." % _TASK_PARAMS['task_id']) return _TASK_PARAMS['task_id'] def _task_get_options(task_id, task_name): """Returns options for the task 'id' read from the BibSched task queue table.""" out = {} res = run_sql("SELECT arguments FROM schTASK WHERE id=%s AND proc LIKE %s", (task_id, task_name+'%')) try: out = marshal.loads(res[0][0]) except: write_message("Error: %s task %d does not seem to exist." \ % (task_name, task_id), sys.stderr) task_update_status('ERROR') sys.exit(1) write_message('Options retrieved: %s' % (out, ), verbose=9) return out def _task_run(task_run_fnc): """Runs the task by fetching arguments from the BibSched task queue. This is what BibSched will be invoking via daemon call. The task prints Fibonacci numbers for up to NUM on the stdout, and some messages on stderr. @param task_run_fnc: will be called as the main core function. Must return False in case of errors. Return True in case of success and False in case of failure.""" ## We prepare the pid file inside /prefix/var/run/taskname_id.pid check_running_process_user() try: pidfile_name = os.path.join(CFG_PREFIX, 'var', 'run', 'bibsched_task_%d.pid' % _TASK_PARAMS['task_id']) pidfile = open(pidfile_name, 'w') pidfile.write(str(os.getpid())) pidfile.close() except OSError: register_exception(alert_admin=True) task_update_status("ERROR") return False ## check task status: task_status = task_read_status() if task_status not in ("WAITING", "SCHEDULED"): write_message("Error: The task #%d is %s. I expected WAITING or SCHEDULED." % (_TASK_PARAMS['task_id'], task_status), sys.stderr) return False time_now = time.time() if _TASK_PARAMS['runtime_limit'] is not None and os.environ.get('BIBSCHED_MODE', 'manual') != 'manual': if not _TASK_PARAMS['runtime_limit'][0][0] <= time_now <= _TASK_PARAMS['runtime_limit'][0][1]: if time_now <= _TASK_PARAMS['runtime_limit'][0][0]: new_runtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(_TASK_PARAMS['runtime_limit'][0][0])) else: new_runtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(_TASK_PARAMS['runtime_limit'][1][0])) progress = run_sql("SELECT progress FROM schTASK WHERE id=%s", (_TASK_PARAMS['task_id'], )) if progress: progress = progress[0][0] else: progress = '' g = re.match(r'Postponed (\d+) time\(s\)', progress) if g: postponed_times = int(g.group(1)) else: postponed_times = 0 run_sql("UPDATE schTASK SET runtime=%s, status='WAITING', progress=%s WHERE id=%s", (new_runtime, 'Postponed %d time(s)' % (postponed_times + 1), _TASK_PARAMS['task_id'])) write_message("Task #%d postponed because outside of runtime limit" % _TASK_PARAMS['task_id']) return True ## initialize signal handler: signal.signal(signal.SIGUSR2, signal.SIG_IGN) signal.signal(signal.SIGTSTP, _task_sig_sleep) signal.signal(signal.SIGTERM, _task_sig_stop) signal.signal(signal.SIGQUIT, _task_sig_stop) signal.signal(signal.SIGABRT, _task_sig_suicide) signal.signal(signal.SIGINT, _task_sig_stop) ## we can run the task now: write_message("Task #%d started." % _TASK_PARAMS['task_id']) task_update_status("RUNNING") ## run the task: _TASK_PARAMS['task_starting_time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) sleeptime = _TASK_PARAMS['sleeptime'] try: try: if callable(task_run_fnc) and task_run_fnc(): task_update_status("DONE") else: task_update_status("DONE WITH ERRORS") except SystemExit: pass except: register_exception(alert_admin=True) task_update_status("ERROR") finally: task_status = task_read_status() if sleeptime: new_runtime = get_datetime(sleeptime) ## The task is a daemon. We resubmit it if task_status == 'DONE': ## It has finished in a good way. We recycle the database row run_sql("UPDATE schTASK SET runtime=%s, status='WAITING', progress='' WHERE id=%s", (new_runtime, _TASK_PARAMS['task_id'])) write_message("Task #%d finished and resubmitted." % _TASK_PARAMS['task_id']) elif task_status == 'STOPPED': run_sql("UPDATE schTASK SET status='WAITING', progress='' WHERE id=%s", (_TASK_PARAMS['task_id'], )) write_message("Task #%d stopped and resubmitted." % _TASK_PARAMS['task_id']) else: ## We keep the bad result and we resubmit with another id. #res = run_sql('SELECT proc,user,sleeptime,arguments,priority FROM schTASK WHERE id=%s', (_TASK_PARAMS['task_id'], )) #proc, user, sleeptime, arguments, priority = res[0] #run_sql("""INSERT INTO schTASK (proc,user, #runtime,sleeptime,status,arguments,priority) #VALUES (%s,%s,%s,%s,'WAITING',%s, %s)""", #(proc, user, new_runtime, sleeptime, arguments, priority)) write_message("Task #%d finished but not resubmitted. [%s]" % (_TASK_PARAMS['task_id'], task_status)) else: ## we are done: write_message("Task #%d finished. [%s]" % (_TASK_PARAMS['task_id'], task_status)) ## Removing the pid os.remove(pidfile_name) return True def _usage(exitcode=1, msg="", help_specific_usage="", description=""): """Prints usage info.""" if msg: sys.stderr.write("Error: %s.\n" % msg) sys.stderr.write("Usage: %s [options]\n" % sys.argv[0]) if help_specific_usage: sys.stderr.write("Command options:\n") sys.stderr.write(help_specific_usage) sys.stderr.write("Scheduling options:\n") sys.stderr.write(" -u, --user=USER\tUser name under which to submit this" " task.\n") sys.stderr.write(" -t, --runtime=TIME\tTime to execute the task. [default=now]\n" "\t\t\tExamples: +15s, 5m, 3h, 2002-10-27 13:57:26.\n") sys.stderr.write(" -s, --sleeptime=SLEEP\tSleeping frequency after" " which to repeat the task.\n" "\t\t\tExamples: 30m, 2h, 1d. [default=no]\n") sys.stderr.write(" -L --limit=LIMIT\tTime limit when it is" " allowed to execute the task.\n" "\t\t\tExamples: 22:00-03:00, Sunday 01:00-05:00.\n" "\t\t\tSyntax: [Wee[kday]] [hh[:mm][-hh[:mm]]].\n") sys.stderr.write(" -P, --priority=PRI\tTask priority (0=default, 1=higher, etc).\n") sys.stderr.write(" -N, --name=NAME\tTask specific name (advanced option).\n") sys.stderr.write("General options:\n") sys.stderr.write(" -h, --help\t\tPrint this help.\n") sys.stderr.write(" -V, --version\t\tPrint version information.\n") sys.stderr.write(" -v, --verbose=LEVEL\tVerbose level (0=min," " 1=default, 9=max).\n") sys.stderr.write(" --profile=STATS\tPrint profile information. STATS is a comma-separated\n\t\t\tlist of desired output stats (calls, cumulative,\n\t\t\tfile, line, module, name, nfl, pcalls, stdname, time).\n") if description: sys.stderr.write(description) sys.exit(exitcode) def _task_sig_sleep(sig, frame): """Signal handler for the 'sleep' signal sent by BibSched.""" signal.signal(signal.SIGTSTP, signal.SIG_IGN) write_message("task_sig_sleep(), got signal %s frame %s" % (sig, frame), verbose=9) write_message("sleeping as soon as possible...") _db_login(1) task_update_status("ABOUT TO SLEEP") def _task_sig_stop(sig, frame): """Signal handler for the 'stop' signal sent by BibSched.""" write_message("task_sig_stop(), got signal %s frame %s" % (sig, frame), verbose=9) write_message("stopping as soon as possible...") _db_login(1) # To avoid concurrency with an interrupted run_sql call task_update_status("ABOUT TO STOP") def _task_sig_suicide(sig, frame): """Signal handler for the 'suicide' signal sent by BibSched.""" write_message("task_sig_suicide(), got signal %s frame %s" % (sig, frame), verbose=9) write_message("suiciding myself now...") task_update_status("SUICIDING") write_message("suicided") _db_login(1) task_update_status("SUICIDED") sys.exit(1) def _task_sig_dumb(sig, frame): """Dumb signal handler.""" pass _RE_PSLINE = re.compile('^\s*(.+?)\s+(.+?)\s*$') def guess_apache_process_user_from_ps(): """Guess Apache process user by parsing the list of running processes.""" apache_users = [] try: # Tested on Linux, Sun and MacOS X for line in os.popen('ps -A -o user,comm').readlines(): g = _RE_PSLINE.match(line) if g: username = g.group(2) process = os.path.basename(g.group(1)) if process in ('apache', 'apache2', 'httpd') : if username not in apache_users and username != 'root': apache_users.append(username) except Exception, e: print >> sys.stderr, "WARNING: %s" % e return tuple(apache_users) def guess_apache_process_user(): """ Return the possible name of the user running the Apache server process. (Look at running OS processes or look at OS users defined in /etc/passwd.) """ apache_users = guess_apache_process_user_from_ps() + ('apache2', 'apache', 'www-data') for username in apache_users: try: userline = pwd.getpwnam(username) return userline[0] except KeyError: pass print >> sys.stderr, "ERROR: Cannot detect Apache server process user. Please set the correct value in CFG_BIBSCHED_PROCESS_USER." sys.exit(1) def check_running_process_user(): """ Check that the user running this program is the same as the user configured in CFG_BIBSCHED_PROCESS_USER or as the user running the Apache webserver process. """ running_as_user = pwd.getpwuid(os.getuid())[0] if CFG_BIBSCHED_PROCESS_USER: # We have the expected bibsched process user defined in config, # so check against her, not against Apache. if running_as_user != CFG_BIBSCHED_PROCESS_USER: print >> sys.stderr, """ERROR: You must run "%(x_proc)s" as the user set up in your CFG_BIBSCHED_PROCESS_USER (seems to be "%(x_user)s"). You may want to do "sudo -u %(x_user)s %(x_proc)s ..." to do so. If you think this is not right, please set CFG_BIBSCHED_PROCESS_USER appropriately and rerun "inveniocfg --update-config-py".""" % \ {'x_proc': os.path.basename(sys.argv[0]), 'x_user': CFG_BIBSCHED_PROCESS_USER} sys.exit(1) elif running_as_user != guess_apache_process_user(): # not defined in config, check against Apache print >> sys.stderr, """ERROR: You must run "%(x_proc)s" as the same user that runs your Apache server process (seems to be "%(x_user)s"). You may want to do "sudo -u %(x_user)s %(x_proc)s ..." to do so. If you think this is not right, please set CFG_BIBSCHED_PROCESS_USER appropriately and rerun "inveniocfg --update-config-py".""" % \ {'x_proc': os.path.basename(sys.argv[0]), 'x_user': guess_apache_process_user()} sys.exit(1) return diff --git a/modules/miscutil/demo/Makefile.am b/modules/miscutil/demo/Makefile.am index 3f9239a92..e54f5d92c 100644 --- a/modules/miscutil/demo/Makefile.am +++ b/modules/miscutil/demo/Makefile.am @@ -1,28 +1,26 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. sqldir = $(libdir)/sql/invenio sql_DATA = democfgdata.sql tmpdir = $(prefix)/var/tmp -tmp_DATA = demobibdata.xml \ - demo-site-apache-user-passwords \ - demo-site-apache-user-groups +tmp_DATA = demobibdata.xml EXTRA_DIST = $(sql_DATA) $(tmp_DATA) CLEANFILES = *~ *.tmp diff --git a/modules/miscutil/demo/demo-site-apache-user-groups b/modules/miscutil/demo/demo-site-apache-user-groups deleted file mode 100644 index 4e67bfc2f..000000000 --- a/modules/miscutil/demo/demo-site-apache-user-groups +++ /dev/null @@ -1 +0,0 @@ -theses: jekyll \ No newline at end of file diff --git a/modules/miscutil/demo/demo-site-apache-user-passwords b/modules/miscutil/demo/demo-site-apache-user-passwords deleted file mode 100644 index 9731dd30d..000000000 --- a/modules/miscutil/demo/demo-site-apache-user-passwords +++ /dev/null @@ -1,2 +0,0 @@ -jekyll:sQcN.VNjOmFLo -hyde:j/QjLyXt8iWYs \ No newline at end of file diff --git a/modules/miscutil/lib/errorlib.py b/modules/miscutil/lib/errorlib.py index 06cec64ac..126d2bf7d 100644 --- a/modules/miscutil/lib/errorlib.py +++ b/modules/miscutil/lib/errorlib.py @@ -1,639 +1,644 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ Error handling library """ __revision__ = "$Id$" import traceback import os import sys import time import re from cStringIO import StringIO from invenio.config import CFG_SITE_LANG, CFG_LOGDIR, \ CFG_WEBALERT_ALERT_ENGINE_EMAIL, CFG_SITE_ADMIN_EMAIL, \ CFG_SITE_SUPPORT_EMAIL, CFG_SITE_NAME, CFG_SITE_URL, CFG_VERSION, \ CFG_CERN_SITE, CFG_SITE_EMERGENCY_PHONE_NUMBERS, \ CFG_SITE_ADMIN_EMAIL_EXCEPTIONS from invenio.miscutil_config import CFG_MISCUTIL_ERROR_MESSAGES from invenio.urlutils import wash_url_argument from invenio.messages import wash_language, gettext_set_language from invenio.dateutils import convert_datestruct_to_datetext ## Regular expression to match possible password related variable that should ## be disclosed in frame analysis. RE_PWD = re.compile(r"pwd|pass", re.I) def get_client_info(req): """ Returns a dictionary with client information @param req: mod_python request """ try: return { 'host': req.hostname, 'url': req.unparsed_uri, 'time': convert_datestruct_to_datetext(time.localtime()), 'browser': 'User-Agent' in req.headers_in and \ req.headers_in['User-Agent'] or "N/A", 'client_ip': req.remote_ip} except: return {} def get_pretty_wide_client_info(req): """Return in a pretty way all the avilable information about the current user/client""" if req: from invenio.webuser import collect_user_info user_info = collect_user_info(req) keys = user_info.keys() keys.sort() max_key = max([len(key) for key in keys]) ret = "" fmt = "%% %is: %%s\n" % max_key for key in keys: if key in ('uri', 'referer'): ret += fmt % (key, "<%s>" % user_info[key]) else: ret += fmt % (key, user_info[key]) if ret.endswith('\n'): return ret[:-1] else: return ret else: return "No client information available" def get_tracestack(): """ If an exception has been caught, return the system tracestack or else return tracestack of what is currently in the stack """ if traceback.format_tb(sys.exc_info()[2]): delimiter = "\n" tracestack_pretty = "Traceback: \n%s" % \ delimiter.join(traceback.format_tb(sys.exc_info()[2])) else: ## force traceback except for this call tracestack = traceback.extract_stack()[:-1] tracestack_pretty = "%sForced traceback (most recent call last)" % \ (' '*4, ) for trace_tuple in tracestack: tracestack_pretty += """ File "%(file)s", line %(line)s, in %(function)s %(text)s""" % { 'file': trace_tuple[0], 'line': trace_tuple[1], 'function': trace_tuple[2], 'text': trace_tuple[3] is not None and \ str(trace_tuple[3]) or ""} return tracestack_pretty def send_sms(phone_number, msg): """Send msg as an SMS to each phone number. Note: this function is just an example and works only at CERN it should be reimplemented in your own instituition. """ if not CFG_CERN_SITE: raise NotImplementedError("Implement this function " "with your own method") if phone_number[0] == '+': phone_number = '00' + phone_number[1:] if phone_number[0] != '0': phone_number = '00' + phone_number from invenio.mailutils import send_email return send_email(CFG_SITE_SUPPORT_EMAIL, phone_number + '@mail2sms.cern.ch', '', msg, header='', footer='') def register_emergency(msg, send_sms_function=send_sms): """Launch an emergency. This means to send sms messages to each phone number in CFG_SITE_EMERGENCY_PHONE_NUMBERS.""" for phone_number in CFG_SITE_EMERGENCY_PHONE_NUMBERS: send_sms_function(phone_number, msg) def find_all_values_to_hide(local_variables, analyzed_stack=None): """Return all the potential password to hyde.""" ## Let's add at least the DB password. if analyzed_stack is None: from invenio.dbquery import CFG_DATABASE_PASS ret = set([CFG_DATABASE_PASS]) analyzed_stack = set() else: ret = set() for key, value in local_variables.iteritems(): if id(value) in analyzed_stack: ## Let's avoid loops continue analyzed_stack.add(id(value)) if RE_PWD.search(key): ret.add(str(value)) if isinstance(value, dict): ret |= find_all_values_to_hide(value, analyzed_stack) + if '' in ret: + ## Let's discard the empty string in case there is an empty password, + ## or otherwise anything will be separated by '<*****>' in the output + ## :-) + ret.remove('') return ret def get_pretty_traceback(req=None, exc_info=None, force_stack=True): """ Given an optional request object and an optional exc_info, returns a text string representing many details about an exception. """ if exc_info is None: exc_info = sys.exc_info() if exc_info[0]: ## We found an exception. ## We want to extract the name of the Exception exc_name = exc_info[0].__name__ exc_value = str(exc_info[1]) ## Let's record when and where and what www_data = "%(time)s -> %(name)s: %(value)s" % { 'time': time.strftime("%Y-%m-%d %H:%M:%S"), 'name': exc_name, 'value': exc_value} ## Let's retrieve contextual user related info, if any try: client_data = get_pretty_wide_client_info(req) except Exception, err: client_data = "Error in retrieving " \ "contextual information: %s" % err ## Let's extract the traceback: if not exc_name.startswith('Invenio') or force_stack: tracestack_data_stream = StringIO() tb = sys.exc_info()[2] while 1: if not tb.tb_next: break tb = tb.tb_next stack = [] f = tb.tb_frame while f: stack.append(f) f = f.f_back stack.reverse() stack = stack[-10:] ## Let's just take the last few frames traceback.print_exc(file=tracestack_data_stream) print >> tracestack_data_stream, \ "Locals by frame, innermost last" values_to_hide = set() for frame in stack: print >> tracestack_data_stream print >> tracestack_data_stream, \ ">>>> Frame %s in %s at line %s" % ( frame.f_code.co_name, frame.f_code.co_filename, frame.f_lineno) try: values_to_hide |= find_all_values_to_hide(frame.f_locals) code = open(frame.f_code.co_filename).readlines() first_line = max(1, frame.f_lineno-3) last_line = min(len(code), frame.f_lineno+3) print >> tracestack_data_stream, "*" * 79 for line in xrange(first_line, last_line+1): code_line = code[line-1].rstrip() for value in values_to_hide: ## Let's hide passwords code_line = code_line.replace(value, '<*****>') if line == frame.f_lineno: print >> tracestack_data_stream, \ "----> %4i %s" % (line, code_line) else: print >> tracestack_data_stream, \ " %4i %s" % (line, code_line) print >> tracestack_data_stream, "*" * 79 except: pass for key, value in frame.f_locals.items(): print >> tracestack_data_stream, "\t%20s = " % key, for to_hide in values_to_hide: ## Let's hide passwords value = repr(value).replace(to_hide, '<*****>') try: print >> tracestack_data_stream, \ _truncate_dynamic_string(value) except: print >> tracestack_data_stream, \ "" tracestack_data = tracestack_data_stream.getvalue() else: tracestack_data = '' ## Okay, start printing: output = StringIO() print >> output, '\n', print >> output, "The following problem occurred on <%s>" \ " (Invenio %s)" % (CFG_SITE_URL, CFG_VERSION) print >> output, "\n>> %s" % www_data print >> output, "\n>>> User details\n" print >> output, client_data if tracestack_data: print >> output, "\n>>> Traceback details\n" print >> output, tracestack_data return output.getvalue() else: return "" def register_exception(force_stack=False, stream='error', req=None, prefix='', suffix='', alert_admin=False, subject=''): """ Log error exception to invenio.err and warning exception to invenio.log. Errors will be logged together with client information (if req is given). Note: For sanity reasons, dynamic params such as PREFIX, SUFFIX and local stack variables are checked for length, and only first 500 chars of their values are printed. @param force_stack: when True stack is always printed, while when False, stack is printed only whenever the Exception type is not containing the word Invenio @param stream: 'error' or 'warning' @param req: mod_python request @param prefix: a message to be printed before the exception in the log @param suffix: a message to be printed before the exception in the log @param alert_admin: wethever to send the exception to the administrator via email. Note this parameter is bypassed when CFG_SITE_ADMIN_EMAIL_EXCEPTIONS is set to a value different than 1 @param subject: overrides the email subject @return: 1 if successfully wrote to stream, 0 if not """ try: ## Let's extract exception information exc_info = sys.exc_info() output = get_pretty_traceback( req=req, exc_info=exc_info, force_stack=force_stack) if output: ## Okay, start printing: log_stream = StringIO() email_stream = StringIO() print >> email_stream, '\n', ## If a prefix was requested let's print it if prefix: prefix = _truncate_dynamic_string(prefix) print >> log_stream, prefix + '\n' print >> email_stream, prefix + '\n' print >> log_stream, output print >> email_stream, output ## If a suffix was requested let's print it if suffix: suffix = _truncate_dynamic_string(suffix) print >> log_stream, suffix print >> email_stream, suffix log_text = log_stream.getvalue() email_text = email_stream.getvalue() if email_text.endswith('\n'): email_text = email_text[:-1] ## Preparing the exception dump stream = stream=='error' and 'err' or 'log' ## We now have the whole trace written_to_log = False try: ## Let's try to write into the log. open(os.path.join(CFG_LOGDIR, 'invenio.' + stream), 'a').write( log_text) written_to_log = True except: written_to_log = False if CFG_SITE_ADMIN_EMAIL_EXCEPTIONS > 1 or \ (alert_admin and CFG_SITE_ADMIN_EMAIL_EXCEPTIONS > 0) or \ not written_to_log: ## If requested or if it's impossible to write in the log from invenio.mailutils import send_email if not subject: filename, line_no, function_name = _get_filename_and_line(exc_info) subject = 'Exception (%s:%s:%s)' % (filename, line_no, function_name) subject = '%s at %s' % (subject, CFG_SITE_URL) if not written_to_log: email_text += """\ Note that this email was sent to you because it has been impossible to log this exception into %s""" % os.path.join(CFG_LOGDIR, 'invenio.' + stream) send_email( CFG_SITE_ADMIN_EMAIL, CFG_SITE_ADMIN_EMAIL, subject=subject, content=email_text) return 1 else: return 0 except Exception, err: print >> sys.stderr, "Error in registering exception to '%s': '%s'" % ( CFG_LOGDIR + '/invenio.' + stream, err) return 0 def register_errors(errors_or_warnings_list, stream, req=None): """ log errors to invenio.err and warnings to invenio.log errors will be logged with client information (if req is given) and a tracestack warnings will be logged with just the warning message @param errors_or_warnings_list: list of tuples (err_name, err_msg) err_name = ERR_ + %(module_directory_name)s + _ + %(error_name)s #ALL CAPS err_name must be stored in file: module_directory_name + _config.py as the key for dict with name: CFG_ + %(module_directory_name)s + _ERROR_MESSAGES @param stream: 'error' or 'warning' @param req: mod_python request @return: tuple integer 1 if successfully wrote to stream, integer 0 if not will append another error to errors_list if unsuccessful """ client_info_dict = "" if stream == "error": # call the stack trace now tracestack_pretty = get_tracestack() # if req is given, get client info if req: client_info_dict = get_client_info(req) if client_info_dict: client_info = \ '''URL: http://%(host)s%(url)s Browser: %(browser)s Client: %(client_ip)s''' % client_info_dict else: client_info = "No client information available" else: client_info = "No client information available" # check arguments errors_or_warnings_list = wash_url_argument( errors_or_warnings_list, 'list') stream = wash_url_argument(stream, 'str') for etuple in errors_or_warnings_list: etuple = wash_url_argument(etuple, 'tuple') # check stream arg for presence of [error,warning]; when none, add error # and default to warning if stream == 'error': stream = 'err' elif stream == 'warning': stream = 'log' else: stream = 'log' error = 'ERR_MISCUTIL_BAD_FILE_ARGUMENT_PASSED' errors_or_warnings_list.append( (error, eval(CFG_MISCUTIL_ERROR_MESSAGES[error])% stream)) # update log_errors stream_location = os.path.join(CFG_LOGDIR, 'invenio.' + stream) errors = '' for etuple in errors_or_warnings_list: try: errors += "%s%s : %s \n " % (' '*4*7+' ', etuple[0], etuple[1]) except: errors += "%s%s \n " % (' '*4*7+' ', etuple) if errors: errors = errors[(4*7+1):-3] # get rid of begining spaces and last '\n' msg = """ %(time)s --> %(errors)s%(error_file)s""" % { 'time': client_info_dict and client_info_dict['time'] or \ time.strftime("%Y-%m-%d %H:%M:%S"), 'errors': errors, 'error_file': stream=='err' and "\n%s%s\n%s\n" % ( ' '*4, client_info, tracestack_pretty) or ""} try: stream_to_write = open(stream_location, 'a+') stream_to_write.writelines(msg) stream_to_write.close() return_value = 1 except: error = 'ERR_MISCUTIL_WRITE_FAILED' errors_or_warnings_list.append( (error, CFG_MISCUTIL_ERROR_MESSAGES[error] % stream_location)) return_value = 0 return return_value def get_msg_associated_to_code(err_code, stream='error'): """ Returns string of code @param err_code: error or warning code @type err_code: string @param stream: 'error' or 'warning' @type stream: string @return: (err_code, formatted_message) @rtype: tuple """ err_code = wash_url_argument(err_code, 'str') stream = wash_url_argument(stream, 'str') try: module_directory_name = err_code.split('_')[1].lower() module_config = module_directory_name + '_config' module_dict_name = "CFG_" + module_directory_name.upper() + \ "_%s_MESSAGES" % stream.upper() module = __import__( module_config, globals(), locals(), [module_dict_name]) module_dict = getattr(module, module_dict_name) err_msg = module_dict[err_code] except ImportError: error = 'ERR_MISCUTIL_IMPORT_ERROR' err_msg = CFG_MISCUTIL_ERROR_MESSAGES[error] % (err_code, module_config) err_code = error except AttributeError: error = 'ERR_MISCUTIL_NO_DICT' err_msg = CFG_MISCUTIL_ERROR_MESSAGES[error] % (err_code, module_config, module_dict_name) err_code = error except KeyError: error = 'ERR_MISCUTIL_NO_MESSAGE_IN_DICT' err_msg = CFG_MISCUTIL_ERROR_MESSAGES[error] % (err_code, module_config + '.' + module_dict_name) err_code = error except: error = 'ERR_MISCUTIL_UNDEFINED_ERROR' err_msg = CFG_MISCUTIL_ERROR_MESSAGES[error] % err_code err_code = error return (err_code, err_msg) def get_msgs_for_code_list(code_list, stream='error', ln=CFG_SITE_LANG): """ @param code_list: list of tuples [(err_name, arg1, ..., argN), ...] err_name = ERR_ + %(module_directory_name)s + _ + %(error_name)s #ALL CAPS err_name must be stored in file: module_directory_name + _config.py as the key for dict with name: CFG_ + %(module_directory_name)s + _ERROR_MESSAGES For warnings, same thing except: err_name can begin with either 'ERR' or 'WRN' dict name ends with _warning_messages @param stream: 'error' or 'warning' @return: list of tuples of length 2 [('ERR_...', err_msg), ...] if code_list empty, will return None. if errors retrieving error messages, will append an error to the list """ ln = wash_language(ln) _ = gettext_set_language(ln) out = [] if type(code_list) is None: return None code_list = wash_url_argument(code_list, 'list') stream = wash_url_argument(stream, 'str') for code_tuple in code_list: if not(type(code_tuple) is tuple): code_tuple = (code_tuple, ) nb_tuple_args = len(code_tuple) - 1 err_code = code_tuple[0] if stream == 'error' and not err_code.startswith('ERR'): error = 'ERR_MISCUTIL_NO_ERROR_MESSAGE' out.append((error, eval(CFG_MISCUTIL_ERROR_MESSAGES[error]))) continue elif stream == 'warning' and not (err_code.startswith('ERR') or \ err_code.startswith('WRN')): error = 'ERR_MISCUTIL_NO_WARNING_MESSAGE' out.append((error, eval(CFG_MISCUTIL_ERROR_MESSAGES[error]))) continue (new_err_code, err_msg) = get_msg_associated_to_code(err_code, stream) if err_msg[:2] == '_(' and err_msg[-1] == ')': # err_msg is internationalized err_msg = eval(err_msg) nb_msg_args = err_msg.count('%') - err_msg.count('%%') parsing_error = "" if new_err_code != err_code or nb_msg_args == 0: # undefined_error or immediately displayable error out.append((new_err_code, err_msg)) continue try: if nb_msg_args == nb_tuple_args: err_msg = err_msg % code_tuple[1:] elif nb_msg_args < nb_tuple_args: err_msg = err_msg % code_tuple[1:nb_msg_args+1] parsing_error = 'ERR_MISCUTIL_TOO_MANY_ARGUMENT' parsing_error_message = eval( CFG_MISCUTIL_ERROR_MESSAGES[parsing_error]) parsing_error_message %= code_tuple[0] elif nb_msg_args > nb_tuple_args: code_tuple = list(code_tuple) for dummy in range(nb_msg_args - nb_tuple_args): code_tuple.append('???') code_tuple = tuple(code_tuple) err_msg = err_msg % code_tuple[1:] parsing_error = 'ERR_MISCUTIL_TOO_FEW_ARGUMENT' parsing_error_message = eval( CFG_MISCUTIL_ERROR_MESSAGES[parsing_error]) parsing_error_message %= code_tuple[0] except: parsing_error = 'ERR_MISCUTIL_BAD_ARGUMENT_TYPE' parsing_error_message = eval( CFG_MISCUTIL_ERROR_MESSAGES[parsing_error]) parsing_error_message %= code_tuple[0] out.append((err_code, err_msg)) if parsing_error: out.append((parsing_error, parsing_error_message)) if not(out): out = None return out def send_error_report_to_admin(header, url, time_msg, browser, client, error, sys_error, traceback_msg): """ Sends an email to the admin with client info and tracestack """ from_addr = '%s Alert Engine <%s>' % ( CFG_SITE_NAME, CFG_WEBALERT_ALERT_ENGINE_EMAIL) to_addr = CFG_SITE_ADMIN_EMAIL body = """ The following error was seen by a user and sent to you. %(contact)s %(header)s %(url)s %(time)s %(browser)s %(client)s %(error)s %(sys_error)s %(traceback)s Please see the %(logdir)s/invenio.err for traceback details.""" % { 'header': header, 'url': url, 'time': time_msg, 'browser': browser, 'client': client, 'error': error, 'sys_error': sys_error, 'traceback': traceback_msg, 'logdir': CFG_LOGDIR, 'contact': "Please contact %s quoting the following information:" % (CFG_SITE_SUPPORT_EMAIL, )} from invenio.mailutils import send_email send_email(from_addr, to_addr, subject="Error notification", content=body) def _get_filename_and_line(exc_info): """ Return the filename, the line and the function_name where the exception happened. """ tb = exc_info[2] exception_info = traceback.extract_tb(tb)[-1] filename = os.path.basename(exception_info[0]) line_no = exception_info[1] function_name = exception_info[2] return filename, line_no, function_name def _truncate_dynamic_string(val, maxlength=500): """ Return at most MAXLENGTH characters of VAL. Useful for sanitizing dynamic variable values in the output. """ out = repr(val) if len(out) > maxlength: out = out[:maxlength] + ' [...]' return out diff --git a/modules/webaccess/doc/admin/webaccess-admin-guide.webdoc b/modules/webaccess/doc/admin/webaccess-admin-guide.webdoc index 9a84e4be8..c72418353 100644 --- a/modules/webaccess/doc/admin/webaccess-admin-guide.webdoc +++ b/modules/webaccess/doc/admin/webaccess-admin-guide.webdoc @@ -1,1145 +1,1136 @@ ## -*- mode: html; coding: utf-8; -*- ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

Contents

1. Introduction, using roles
2. WebAccess admin interface
3. Example pages, illustrating snapshots
4. Managing accounts / Access policy
5. Managing login methods
6. Firewall-like role definitions

1. INTRODUCTION, USING ROLES

   WebAccess is a common RBAC, role based access control, for all of
   Invenio. This means that users are connected to roles that cover
   different areas of access. I.e administrator of the photo
   collection or system librarian. Users can be active in
   different areas and of course connected to as many roles as needed.
 
   The roles are connected to actions. An action identifies a task you
   can perform in Invenio. It can be defined to take any number of
   arguments in order to more clearly describe what you are allowing
   connected users to do.
 
   For example the system librarian can be allowed to run bibindex on
   the different indexes. To allow system librarians to run the
   bibindex indexing on the field author we connect role system
   librarian with action runbibindex using the argument
   index='author'.
 
   Additionally, roles could have firewall-like role
   definitions. A definition is a formal description of which
   users are entitled to belong to the role. So you have two ways for
   connecting users to roles. Either linking explicitly a user with the
   role or describing the characteristics that makes users belong to
   the role.
 
   WebAccess is based on allowing users to perform actions. This means
   that only allowed actions are stored in the access control engine's
   database.
 

2. WEBACCESS ADMIN INTERFACE

 All the WebAccess Administration web pages have certain
 features/design choices in common
 
 - Divided into steps
 
   The process of adding new authorizations/information is
   stepwise. The subtitle contains information about wich step you are
   on and what you are supposed to do.
 
 - Restart from any wanted step
 
   You can always start from an earlier step by simply clicking the
   wanted button. This is not a way to undo changes! No information
   about previous database is kept, so all changes are definite.
 
 - Change or new entry must confirmed
 
   On all the pages you will be asked to confirm the change, with
   information about what kind of change you are about to perform.
 
 - Links to other relevant admin areas on the right side
 
   To make it easier to perform your administration tasks, we have
   added a menu area on the right hand side of these pages. The menu
   contain links to other relevant admin pages and change according to
   the page you are on and the information you have selected.
 

3. EXAMPLE PAGES

 I. Role area
 II. Example - connecting role and user
 
 
 I. Role area
 
   Administration tasks starts in one of the administration areas. The
   role area is the main area from where you can perform all your
   managing tasks. The other admin areas are just other ways of
   entering.
 
 

Role Administration

administration with roles as access point
Users:
add or remove users from the access to a role and its priviliges.
Authorizations/Actions:
these terms means almost the same, but an authorization is a
connection between a role and an action (possibly) containing arguments.
Roles:
see all the information attached to a role and decide if you want to
delete it.
- +
id name description definition users authorizations / actions role
2 photoadmin Photo collection administrator None add / delete add / modify / remove modify / delete show details
1 superadmin superuser with all rights allow email /.*@cern.ch/ add / delete add / modify / remove modify / delete show details
3 webaccessadmin WebAccess administratorallow apache_user 'jekyll' - de...allow nickname 'jekyll' add / delete add / modify / remove modify / delete show details
Create new role
go here to add a new role.
Create new action
go here to add a new action.
 
 II. Example - connecting role and user
 
   One of the important tasks that can be handled via the WebAccess Admin Web Interface
   is the delegation of access rights to users. This is done by connecting them to the
   different roles offered.
 
   The task is divided into 5 simple and comprehensive steps. Below follows the pages from
   the different steps with comments on the ongoing procedure.
 
 - step 1 - select a role
 
   You must first select the role you want to connect users to. All the available roles are
   listed alfabetically in a select box. Just find the wanted role and select it. Then click on
   the button saying "select role".
 
   If you start from the Role Area, this step is already done, and you start directly on step 2.
 
 

Connect user to role

step 1 - select a role
1. select role
Create new role
go here to add a new role.
 
 - step 2 - search for users
 
   As you can see, the subtitle of the page has now changed. The subtitle always tells you
   which step you are on and what your current task is.
 
   There can be possibly thousands of users using your online library, therefore it is important
   to make it easier to identify the user you are looking for. Give part of, or the entire search
   string and all users with partly matching e-mails will be listed on the next step.
 
   You can also see that the right hand menu has changed. This area is always updated with links
   to related admin areas.
 
 

Connect user to role

step 2 - search for users
1. select role
2. search pattern
Create new role
go here to add a new role.
Remove users
remove users from role superadmin.
Connected users
show all users connected to role superadmin.
Add authorization
start adding new authorizations to role superadmin.
 
 - step 3 - select a user.
 
   The select box contains all users with partly matching e-mail addresses. Select the one
   you want to connect to the role and continue.
 
   Notice the navigation trail that tells you were on the Administrator pages you are currently
   working.
 
 

Connect user to role

step 3 - select a user
1. select role
2. search pattern
3. select user
Create new role
go here to add a new role.
Remove users
remove users from role superadmin.
Connected users
show all users connected to role superadmin.
Add authorization
start adding new authorizations to role superadmin.
 
 - step 4 - confirm to add user
 
   All WebAccess Administrator web pages display the action you are about to peform, this
   means explaining what kind of addition, change or update will be done to your access control
   data.
 
   If you are happy with your decision, simply confirm it.
 
 

Connect user to role

step 4 - confirm to add user
1. select role
2. search pattern
3. select user
add user mikael.vik@cern.ch to role superadmin?
Create new role
go here to add a new role.
Remove users
remove users from role superadmin.
Connected users
show all users connected to role superadmin.
Add authorization
start adding new authorizations to role superadmin.
 
 - step 5 - confirm user added.
 
   The user has now been added to this role. You can easily continue adding more users to this
   role be restarting from step 2 or 3. You can also go directly to another area and keep working
   on the same role.
 
 

Connect user to role

step 5 - confirm user added
1. select role
2. search pattern
3. select user
add user mikael.vik@cern.ch to role superadmin?

confirm: user mikael.vik@cern.ch added to role superadmin.

Create new role
go here to add a new role.
Remove users
remove users from role superadmin.
Connected users
show all users connected to role superadmin.
Add authorization
start adding new authorizations to role superadmin.
 
 - we are done
 
   This example is very similar to all the other pages where you administrate WebAccess. The pages
   are an easy gateway to maintaing access control rights and share a lot of features.
   - divided into steps
   - restart from any wanted step (not undo)
   - changes must be confirmed
   - link to other relevant areas
   - prevent unwanted input
 
   As an administrator with access to these pages you are free to manage the rights any way you want.
 

IV. Managing accounts and access policy

   Here you can administrate the accounts and the access policy for your Invenio installation.
 
   - Access policy:
 
     To change the access policy, the general config file (or
     access_control_config.py) must be edited manually in a text
     editor. The site can there be defined as opened or closed, you can
     edit the access policy level for guest accounts, registered
     accounts and decide when to warn the owner of the account when
     something happens with it, either when it is created, deleted or
     approved.  The Apache server must be restarted after modifying
     these settings.
 
     The two levels for guest account, are:
        0 - Allow guest accounts
        1 - Do not allow guest accounts
     The five levels for normal accounts, are:
        0 - Allow user to create account, automatically activate new accounts
        1 - Allow user to create account, administrator must activate account
        2 - Only administrators can create account. User cannot edit the email address.
        3 - Users cannot register or update account information (email/password)
        4 - User cannot change default login method
     You can configure Invenio to send an email:
        1. To an admin email-address when an account is created
        2. To the owner of an account when it is created
        3. To the owner of an account when it is activated
        4. To the owner of an account when it is deleted
 
     Define how open the site is:
       0 = normal operation of the site
       1 = read-only site, all write operations temporarily closed
       2 = site fully closed
       3 = database connections disabled
       CFG_ACCESS_CONTROL_LEVEL_SITE = 0
     Access policy for guests:
       0 = Allow guests to search,
       1 = Guests cannot search (all users must login)
       CFG_ACCESS_CONTROL_LEVEL_GUESTS = 0
     Access policy for accounts:
       0 = Users can register, automatically acticate accounts
       1 = Users can register, but admin must activate the accounts
       2 = Users cannot register or change email address, only admin can register accounts.
       3 = Users cannot register or update email address or password, only admin can register accounts.
       4 = Same as 3, but user cannot change login method.
       CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS = 0
 
     Limit email addresses available to use when register a new account (example: cern.ch):
       CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN = ""
 
     Send an email when a new account is created by an user:
       CFG_ACCESS_CONTROL_NOTIFY_ADMIN_ABOUT_NEW_ACCOUNTS = 0
 
     Send an email to the user notifying when the account is created:
       CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT = 0
 
     Send an email to the user notifying when the account is activated:
       CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_ACTIVATION = 0
 
     Send an email to the user notifying when the account is deleted/rejected:
       CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_DELETION = 0
 
   - Account overview:
     Here you find an overview of the number of guest accounts, registered accounts and accounts
     awaiting activation, with a link to the activation page.
 
   - Create account:
     For creating new accounts, the email address must be unique. If configured to do so, an email
     will be sent to the given address when an account is created.
 
   - Edit accounts:
     For activating or rejecting accounts in addition to modifying them. An activated account can be
     inactivated for a short period of time, but this will not warn the account owner. To find accounts
     enter a part of the email address of the account and then search. This may take some time. If there
     are more than the selected number of accounts per page, you can use the next/prev links to switch
     pages. The accounts to search in can also be limited to only activated or not activated accounts.
 
   - Edit account:
     When editing one account, you can change the email address, password, delete the account, or modify
     the baskets or alerts belonging to one account. Which login method should be the default for this
     account can also be selected. To modify baskets or alerts, you need to login as the user, and
     modify the desired data as a normal user. Remember to log out as the user when you are finished
     editing.
 
 

V. Managing login methods

    Invenio supports using external login systems to authenticate users.
 
    When a user wants to login, the username and password given by the user is checked against the selected
    system, if the user is authenticated by the external system, a valid email-address is returned to
    Invenio and used to recognize the user within Invenio.
 
    If a new user is trying to login without having an account, using an external login system, an account
    is automatically created in Invenio to recognize and store the users settings. The password for the
    local account is randomly generated.
 
    If you want the user to be unable to change login method and account username / password, forcing use
    of certain external systems, set CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS to 4 as mentioned in the last paragraph.
 
    If a user is changing login method from an external one to the internal, he also need to either change the
    password before logging out, or set the password via the lost password email service.
 
    If you are using CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS with a value greater than 1 note
    that, even if the first login of a user through an external authentication technically means registering
    the user into the system, this is not the semantic expected behaviour by the user. The user is already
    registered at an authority that we trust, so there's no need to prevent the user from being imported
    into the system. That's why for external authentication CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS is not
    considered apart from what said above.
 
    If a external login system is used, you may want to protect the users username / password using HTTPS.
 
    To add new system, two changes must be made (for the time being):
-   - The name of the method, if it is default or not, and the classname must be added to the variable
-     CFG_EXTERNAL_AUTHENTICATION in access_control_config.py. Atleast one method must be marked as the
-     default one. The internal login method should be given with None as classname.
-
-     Example:
-      CFG_EXTERNAL_AUTHENTICATION = {"%s (internal)" % CFG_SITE_NAME: (None, True), "CERN NICE (external)":
-                                                        (AuthCernWrapper(), False)}
+   - The name of the method, if it is default or not, and an instance of the class must be added to the variable
+     CFG_EXTERNAL_AUTHENTICATION in access_control_config.py.
 
    - A class must be created derived from the class external_authentication inside file
      external_authentication.py. This class must include at least the
      function auth_user. This function returns a valid email-address in Invenio if the user
      is authenticated, not necessarily the same entered by the user as username. If the user
      is not authenticated, return None.
      The class could also provide five more methods: fetch_user_preferences, user_exists,
      fetch_user_groups_membership and fetch_all_users_groups_membership.
      The first should take an email and eventually a password and should return a dictionary of keys
      and value representing external preferences, infos or settings. If, for some reasons, you like
      to force some kind of hiding for some particular field you should export the related key
      prefixed by "HIDDEN_". Those fields won't be displayed in tables and pages regarding external
      settings.
      The second method should check through the external system if a particular email exists. If you
      provide such a method then a user will be able to switch from and to this authorization method.
      The third method should take an email and (if necessary) a password
      and should return a dictionary of external_groups_names toghether with their description, for which
      the user has a membership. Those groups will be merged into the groups system.
      The user will be a member of those groups and will be able to use them in any place
      where groups are useful, but won't be able to unsubscribe or to administrate them.
      The fourth method should just return a dictionary of external groups as keys and tuples containing
      a group description and a list of email of users belonging to each groups. Those memberships
      will be merged into the database in the way done by the previous method, but could
      provide batch synchronization of groups.
      The fifth method should just return the nickname as is known by the external authentication
      system, given the usual email/username and the password.
      Note: if your system has more than one external login methods then incoherence in the groups
      memberships could happen when a user switch his login method. This will be fixed some times in the
      future.
      If you add as an attribute of your class the enforce_external_nicknames and set it to True, this will enforce
      the system to import external nicknames whenever the user login with the external login method for the
      first time. Since a nickname is not changable this will stay fixed forever. If this nickname is
      already registered in the system (suppose that is linked with a local account) then it will not be
      imported. If this variable doesn't exist or is set to False then no nickname will be
      imported and the user will be free to choose a nickname in the future (and then this will again
      stay forever).
      Note: every method will receive as last parameter the mod_python request object, that could
      be used for particular purposes.
 
 
      Example template:
      from invenio.external_authentication import ExternalAuth, InvenioWebAccessExternalAuthError
 
      class ExternalAuthFoo(ExternalAuth):
          """External authentication template example."""
 
          def __init__ (self):
              """Initialize stuff here."""
              self.name = None
              self.enforce_external_nicknames = False
              pass
 
          def auth_user(self, username, password, req=None):
              """Authenticate user-supplied USERNAME and PASSWORD.
              Return None if authentication failed, or the email address of the
              person if the authentication was successful.  In order to do
              this you may perhaps have to keep a translation table between
              usernames and email addresses.
              Raise InvenioWebAccessExternalAuthError in case of external troubles.
              """
              raise NotImplementedError
              #return None
 
          def user_exists(self, email, req=None):
              """Checks against external_authentication for existance of email.
              @return True if the user exists, False otherwise
              """
              raise NotImplementedError
 
          def fetch_user_groups_membership(self, username, password=None, req=None):
              """Given a username, returns a dictionary of groups
              and their description to which the user is subscribed.
              Raise InvenioWebAccessExternalAuthError in case of troubles.
              """
              raise NotImplementedError
              #return {}
 
          def fetch_user_preferences(self, username, password=None, req=None):
              """Given a username and a password, returns a dictionary of keys and
              values, corresponding to external infos and settings.
 
              userprefs = {"telephone": "2392489",
              "address": "10th Downing Street"}
              """
              raise NotImplementedError
             #return {}
 
          def fetch_all_users_groups_membership(self, req=None):
              """Fetch all the groups with a description, and users who belong to
              each groups.
              @return {'mygroup': ('description', ['email1', 'email2', ...]), ...}
              """
              raise NotImplementedError
 
          def fetch_user_nickname(self, username, password, req=None):
              """Given a username and a password, returns the right nickname belonging
              to that user (username could be an email).
              """
              raise NotImplementedError
              #return Nickname
 

VI. FIREWALL-LIKE ROLE DEFINITIONS

The FireRole language description
     In the WebAccess RBAC system, roles are built up from their names,
     description and definition.
 
     A definition is the way to formally implicitly define which users belong
     to which roles.
 
     A definition is expressed in a firewall like rules language. It's built up
     by rows which are matched from top to bottom, in order to decide if the
     current user (wethever he/she is logged in or not) may belong to a role.
 
     Any row has this syntax:
 
         ALLOW/DENY ANY/ALL
 
         or
 
         ALLOW/DENY FROM/UNTIL "YYYY-MM-DD"
 
         ALLOW/DENY [NOT] field {one or more values}
 
     The rows are parsed from top to bottom. If a row matches the user than the
     user belongs to the role if the rule is an ALLOW rule, otherwise, if the
     rule is a DENY one, the user doesn't belong to the role.
 
     A rule of the kind ALLOW|DENY ANY always matches, regardless of the user.
 
     Note, in place of ANY you can use the word ALL. The semantic is the same. The
     system support both to let the user comply with the English grammar.
 
     The second type of rule is interpreted as follows: given a date in the
     form "YYYY-MM-DD" (double-)quoted), the rule is matched if, when using FROM,
     the current date is either identical or 'bigger' than the given date, or if,
     when using UNTIL, the current date is either identical or 'smaller' than the
     given date. If the rule starts with ALLOW and is matched
     then the next row is evaluated. If it is not matched, then the whole FireRole
     will evaluate into a DENY ALL. If the rule starts with DENY and
     is matched then the whole FireRole while evaluate as a DENY ALL. If it is
     not matched then the next row is evaluated.
 
     The third type of rule is interpreted as follows: given a dictionary
     of keys:values describing a user (we will cover this below), the rule
     considers the value associated with the key named in field, and checks
     if it corresponds to at least one of the values in the "one or more values" list.
     This is a list of comma separated strings, which can be literal
     (double-)quoted strings or regexps (marked by `/' ... `/' signs). If at
     least a value matches (literally or through the regexp language), the
     whole rule is considered to match.
     If the optional NOT keyword is specified than if at least a value of the
     rule matches the rule is skipped, otherwise if all the value of the rules
     don't match the whole rule matches.
 
     A DENY ALL rule is implicitly added at the end of every definition. Note that
     this imply that, if you are using e.g. a temporal rule (FROM/UNTIL), you
     should explicitly add an additional row with value ALLOW ANY,
     if you actually want to allow users in the specified timeframe.
 
     Any field is valid, but only rules concerning fields which currently
     exist in the user describing dictionary are checked. All the rules
     with non existant fields are skipped.
 
     The user describing dictionary (user_info) is built at runtime with all the informations
     that can be gathered about the current user (and its session).
-    Currently valid fields are: uid, email, nickname, apache_user, remote_ip,
-    remote_host, groups, apache_groups and all the external settings provided
+    Currently valid fields are: uid, email, nickname, remote_ip,
+    remote_host, groups and all the external settings provided
     by the external authentication systems (e.g. CERN SSO provides:
     external_authmethod, external_building, external_department, external_email,
     external_external, external_firstname, external_fullname, external_homdir,
     external_homeinstitute, external_lastname, external_login, external_mobilenumber,
     external_phonenumber).
 
     Among those fields there are some special cases, which are remote_ip and
     (apache_)groups. Rules can refer to remote_ip either using a literal
     expression for specifing list of single ips, or a usual regexp (or list
     of regexps), or, also, using the common network group/mask notation
     (e.g. "127.0.0.0/24") as a literal string, which is a mix between literal
     expressions and regexps. (apache_)groups are related to group memberships.
     Since a user will probably belong to more than a group, then the rule
     matches if there's at least one group to which the user belong, that matches
     at least one of the expressions (NOT rules behave as you can imagine).
 
     The dictionary is built using the current user session. If the user is
     authenticated in some way (apache, locally, externally, SSO...) then more
     infos could be provided to the firerole system in order to decide if the
     user should belong to a role or not.
 
     The default fields that are always there are:
     
  • uid: an integer representing the user id
  • nickname: the nickname of the user
  • email: the email of the user
  • group/groups: local or external group to which the user belong
  • guest: 1 if the user is a guest (not logged), 0 otherwise
plus all the external setting retrieved by an external authentication system. If the action to which the role defined is raised from the webinterface of Invenio, then you will have those additional fields:
  • remote_ip: the remote ip address of the user who is browsing
  • remote_host: the remote hostname of the user who is browsing
  • referer: the webpage from where the user is coming from
  • uri: the uri the user is visiting
  • agent: the agent string describing the user's browser
  • -
  • apache_user: the Apache user provided by the authenticated user
  • -
  • apache_group/apache_groups: the Apache groups to which the apache user - belong
Note that you can specify either (apache_)group or (apache_)groups (with or without the trailing s). They are semantically equal and are supported just to let people comply with the English grammar. Every rule is case-insensitive (apart values which must match literally and regexp values which don't explicitly specify case-insesitive matches). Every rule may contain comments preceded by the '#' character. Any comment is discarded. When you set a definition for a role, it is actually compiled and stored in a binary compressed form inside the database. If the syntax isn't correct this will be stated and the definition won't be set or updated. Example of role definition: allow not email /.*@gmail.com/,/.*@hotmail.com/ deny group badguys allow remote_ip "127.0.0.0/24" deny all This definition would match all users whose emails don't end with @gmail.com and @hotmail.com, or who don't belong to the group badguys and have remote_ip in the 24bit mask network of 127.0.0.0. All the the other users don't belong to the role which is being defined. If you want to discover which keys are available on your system to build a FireRole rule, just login with your account in your installation and visit your account page, by activating verbose=9 variable. Under the tile you will se the available keys and values that you can use to build a FireRole rule. All but fields prefixed with precached_ are usuable.
diff --git a/modules/webaccess/lib/Makefile.am b/modules/webaccess/lib/Makefile.am index be8f3035a..43b95c43c 100644 --- a/modules/webaccess/lib/Makefile.am +++ b/modules/webaccess/lib/Makefile.am @@ -1,39 +1,40 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. pylibdir = $(libdir)/python/invenio pylib_DATA = access_control_engine.py \ access_control_config.py \ access_control_admin.py \ access_control_mailcookie.py \ access_control_firerole.py \ access_control_firerole_tests.py \ webaccessadmin_lib.py \ external_authentication_cern.py \ external_authentication.py \ external_authentication_ldap.py \ external_authentication_cern_wrapper.py \ external_authentication_cern_tests.py \ external_authentication_sso.py \ + external_authentication_robot.py \ webaccess_regression_tests.py noinst_DATA = collection_restrictions_migration_kit.py EXTRA_DIST = $(pylib_DATA) $(noinst_DATA) CLEANFILES = *~ *.tmp *.pyc diff --git a/modules/webaccess/lib/access_control_config.py b/modules/webaccess/lib/access_control_config.py index 8309e0869..140f74fb9 100644 --- a/modules/webaccess/lib/access_control_config.py +++ b/modules/webaccess/lib/access_control_config.py @@ -1,296 +1,317 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Invenio Access Control Config. """ __revision__ = \ "$Id$" # pylint: disable=C0301 from invenio.config import CFG_SITE_NAME, CFG_SITE_URL, CFG_SITE_LANG, \ - CFG_SITE_SECURE_URL, CFG_SITE_SUPPORT_EMAIL, CFG_CERN_SITE + CFG_SITE_SECURE_URL, CFG_SITE_SUPPORT_EMAIL, CFG_CERN_SITE, \ + CFG_OPENAIRE_SITE from invenio.messages import gettext_set_language class InvenioWebAccessFireroleError(Exception): """Just an Exception to discover if it's a FireRole problem""" pass # VALUES TO BE EXPORTED # CURRENTLY USED BY THE FILES access_control_engine.py access_control_admin.py webaccessadmin_lib.py # name of the role giving superadmin rights SUPERADMINROLE = 'superadmin' # name of the webaccess webadmin role WEBACCESSADMINROLE = 'webaccessadmin' # name of the action allowing roles to access the web administrator interface WEBACCESSACTION = 'cfgwebaccess' # name of the action allowing roles to access the web administrator interface VIEWRESTRCOLL = 'viewrestrcoll' # name of the action allowing roles to delegate the rights to other roles # ex: libraryadmin to delegate libraryworker DELEGATEADDUSERROLE = 'accdelegaterole' # max number of users to display in the drop down selects MAXSELECTUSERS = 25 # max number of users to display in a page (mainly for user area) MAXPAGEUSERS = 25 # default role definition, source: CFG_ACC_EMPTY_ROLE_DEFINITION_SRC = 'deny all' # default role definition, compiled: -CFG_ACC_EMPTY_ROLE_DEFINITION_OBJ = (False, False, ()) +CFG_ACC_EMPTY_ROLE_DEFINITION_OBJ = (False, ()) # default role definition, compiled and serialized: CFG_ACC_EMPTY_ROLE_DEFINITION_SER = None # List of tags containing (multiple) emails of users who should authorize # to access the corresponding record regardless of collection restrictions. if CFG_CERN_SITE: CFG_ACC_GRANT_AUTHOR_RIGHTS_TO_EMAILS_IN_TAGS = ['859__f', '270__m'] else: CFG_ACC_GRANT_AUTHOR_RIGHTS_TO_EMAILS_IN_TAGS = ['8560_f'] # Use external source for access control? -# Atleast one must be added -# Adviced not to change the name, since it is used to identify the account -# Format is: System name: (System class, Default True/Flase), atleast one -# must be default -CFG_EXTERNAL_AUTHENTICATION = {"Local" : (None, True)} - -# Variables to set to the SSO Authentication name if using SSO -CFG_EXTERNAL_AUTH_USING_SSO = False -CFG_EXTERNAL_AUTH_LOGOUT_SSO = None +# CFG_EXTERNAL_AUTHENTICATION -- this is a dictionary with the enabled login method. +# The key is the name of the login method and the value is an instance of +# of the login method (see /help/admin/webaccess-admin-guide#5). Set the value +# to None if you wish to use the local Invenio authentication method. +# CFG_EXTERNAL_AUTH_DEFAULT -- set this to the key in CFG_EXTERNAL_AUTHENTICATION +# that should be considered as default login method +# CFG_EXTERNAL_AUTH_USING_SSO -- set this to the login method name of an SSO +# login method, if any, otherwise set this to None. +# CFG_EXTERNAL_AUTH_LOGOUT_SSO -- if CFG_EXTERNAL_AUTH_USING_SSO was not None +# set this to the URL that should be contacted to perform an SSO logout + +from invenio.external_authentication_robot import ExternalAuthRobot if CFG_CERN_SITE: - if True: - import external_authentication_sso as ea_sso - CFG_EXTERNAL_AUTH_USING_SSO = "CERN" - # Link to reach in order to logout from SSO - CFG_EXTERNAL_AUTH_LOGOUT_SSO = 'https://login.cern.ch/adfs/ls/?wa=wsignout1.0' - CFG_EXTERNAL_AUTHENTICATION = {CFG_EXTERNAL_AUTH_USING_SSO : (ea_sso.ExternalAuthSSO(), True)} - else: - import external_authentication_cern as ea_cern - CFG_EXTERNAL_AUTHENTICATION = {"Local": (None, False), \ - "CERN": (ea_cern.ExternalAuthCern(), True)} + import external_authentication_sso as ea_sso + CFG_EXTERNAL_AUTH_USING_SSO = "CERN" + CFG_EXTERNAL_AUTH_DEFAULT = CFG_EXTERNAL_AUTH_USING_SSO + CFG_EXTERNAL_AUTH_LOGOUT_SSO = 'https://login.cern.ch/adfs/ls/?wa=wsignout1.0' + CFG_EXTERNAL_AUTHENTICATION = { + CFG_EXTERNAL_AUTH_USING_SSO : ea_sso.ExternalAuthSSO(), + } +elif CFG_OPENAIRE_SITE: + CFG_EXTERNAL_AUTH_DEFAULT = 'Local' + CFG_EXTERNAL_AUTH_USING_SSO = False + CFG_EXTERNAL_AUTH_LOGOUT_SSO = None + CFG_EXTERNAL_AUTHENTICATION = { + "Local": None, + "OpenAIRE": ExternalAuthRobot(enforce_external_nicknames=True, use_zlib=False), + "ZOpenAIRE": ExternalAuthRobot(enforce_external_nicknames=True, use_zlib=True) + } +else: + CFG_EXTERNAL_AUTH_DEFAULT = 'Local' + CFG_EXTERNAL_AUTH_USING_SSO = False + CFG_EXTERNAL_AUTH_LOGOUT_SSO = None + CFG_EXTERNAL_AUTHENTICATION = { + "Local": None, + "Robot": ExternalAuthRobot(enforce_external_nicknames=True, use_zlib=False), + "ZRobot": ExternalAuthRobot(enforce_external_nicknames=True, use_zlib=True) + } + # default data for the add_default_settings function # Note: by default the definition is set to deny any. This won't be a problem # because userid directly connected with roles will still be allowed. # roles # name description definition DEF_ROLES = ((SUPERADMINROLE, 'superuser with all rights', 'deny any'), (WEBACCESSADMINROLE, 'WebAccess administrator', 'deny any'), ('anyuser', 'Any user', 'allow any'), ('basketusers', 'Users who can use baskets', 'allow any'), ('loanusers', 'Users who can use loans', 'allow any'), ('groupusers', 'Users who can use groups', 'allow any'), ('alertusers', 'Users who can use alerts', 'allow any'), ('messageusers', 'Users who can use messages', 'allow any'), ('holdingsusers', 'Users who can view holdings', 'allow any'), ('statisticsusers', 'Users who can view statistics', 'allow any')) # Demo site roles DEF_DEMO_ROLES = (('photocurator', 'Photo collection curator', 'deny any'), - ('thesesviewer', 'Theses viewer', 'allow group "Theses viewers"\nallow apache_group "theses"'), - ('swordcurator', 'BibSword client curator', 'deny any'), + ('thesesviewer', 'Theses viewer', 'allow group "Theses viewers"'), ('thesescurator', 'Theses collection curator', 'deny any'), + ('swordcurator', 'BibSword client curator', 'deny any'), ('referee_DEMOBOO_*', 'Book collection curator', 'deny any'), ('restrictedpicturesviewer', 'Restricted pictures viewer', 'deny any'), ('curator', 'Curator', 'deny any'), ('basketusers', 'Users who can use baskets', 'deny email "hyde@cds.cern.ch"\nallow any'), ('submit_DEMOJRN_*', 'Users who can submit (and modify) "Atlantis Times" articles', 'deny all'), ('atlantiseditor', 'Users who can configure "Atlantis Times" journal', 'deny all'), ('commentmoderator', 'Users who can moderate comments', 'deny all'), ('poetrycommentreader', 'Users who can view comments in Poetry collection', 'deny all')) DEF_DEMO_USER_ROLES = (('jekyll@cds.cern.ch', 'thesesviewer'), ('jekyll@cds.cern.ch', 'swordcurator'), ('dorian.gray@cds.cern.ch', 'referee_DEMOBOO_*'), ('balthasar.montague@cds.cern.ch', 'curator'), ('romeo.montague@cds.cern.ch', 'restrictedpicturesviewer'), ('romeo.montague@cds.cern.ch', 'swordcurator'), ('romeo.montague@cds.cern.ch', 'thesescurator'), ('juliet.capulet@cds.cern.ch', 'restrictedpicturesviewer'), ('juliet.capulet@cds.cern.ch', 'photocurator'), ('romeo.montague@cds.cern.ch', 'submit_DEMOJRN_*'), ('juliet.capulet@cds.cern.ch', 'submit_DEMOJRN_*'), ('balthasar.montague@cds.cern.ch', 'atlantiseditor'), ('romeo.montague@cds.cern.ch', 'poetrycommentreader')) # users # list of e-mail addresses DEF_USERS = [] # actions # name desc allowedkeywords optional DEF_ACTIONS = ( ('cfgwebsearch', 'configure WebSearch', '', 'no'), ('cfgbibformat', 'configure BibFormat', '', 'no'), ('cfgbibknowledge', 'configure BibKnowledge', '', 'no'), ('cfgwebsubmit', 'configure WebSubmit', '', 'no'), ('cfgbibrank', 'configure BibRank', '', 'no'), ('cfgwebcomment', 'configure WebComment', '', 'no'), ('cfgoaiharvest', 'configure OAI Harvest', '', 'no'), ('cfgoairepository', 'configure OAI Repository', '', 'no'), ('cfgbibindex', 'configure BibIndex', '', 'no'), ('cfgbibexport', 'configure BibExport', '', 'no'), + ('cfgrobotkeys', 'configure Robot keys', 'login_method,robot', 'yes'), ('runbibindex', 'run BibIndex', '', 'no'), ('runbibupload', 'run BibUpload', '', 'no'), ('runwebcoll', 'run webcoll', 'collection', 'yes'), ('runbibformat', 'run BibFormat', 'format', 'yes'), ('runbibclassify', 'run BibClassify', 'taxonomy', 'yes'), ('runbibtaskex', 'run BibTaskEx example', '', 'no'), ('runbibrank', 'run BibRank', '', 'no'), ('runoaiharvest', 'run oaiharvest task', '', 'no'), ('runoairepository', 'run oairepositoryupdater task', '', 'no'), ('runbibedit', 'run Record Editor', 'collection', 'yes'), ('runbibeditmulti', 'run Multi-Record Editor', '', 'no'), ('runbibdocfile', 'run Document File Manager', '', 'no'), ('runbibmerge', 'run Record Merger', '', 'no'), ('runbibswordclient', 'run BibSword client', '', 'no'), ('runwebstatadmin', 'run WebStadAdmin', '', 'no'), ('runinveniogc', 'run InvenioGC', '', 'no'), ('runbibexport', 'run BibExport', '', 'no'), ('referee', 'referee document type doctype/category categ', 'doctype,categ', 'yes'), ('submit', 'use webSubmit', 'doctype,act,categ', 'yes'), ('viewrestrdoc', 'view restricted document', 'status', 'no'), ('viewrestrcomment', 'view restricted comment', 'status', 'no'), (WEBACCESSACTION, 'configure WebAccess', '', 'no'), (DELEGATEADDUSERROLE, 'delegate subroles inside WebAccess', 'role', 'no'), (VIEWRESTRCOLL, 'view restricted collection', 'collection', 'no'), ('cfgwebjournal', 'configure WebJournal', 'name,with_editor_rights', 'no'), ('viewcomment', 'view comments', 'collection', 'no'), ('sendcomment', 'send comments', 'collection', 'no'), ('attachcommentfile', 'attach files to comments', 'collection', 'no'), ('attachsubmissionfile', 'upload files to drop box during submission', '', 'no'), ('cfgbibexport', 'configure BibExport', '', 'no'), ('runbibexport', 'run BibExport', '', 'no'), ('usebaskets', 'use baskets', '', 'no'), ('useloans', 'use loans', '', 'no'), ('usegroups', 'use groups', '', 'no'), ('usealerts', 'use alerts', '', 'no'), ('usemessages', 'use messages', '', 'no'), ('viewholdings', 'view holdings', 'collection', 'yes'), ('viewstatistics', 'view statistics', 'collection', 'yes'), ('runbibcirculation', 'run BibCirculation', '', 'no'), ('moderatecomments', 'moderate comments', 'collection', 'no'), ('runbatchuploader', 'run batchuploader', 'collection', 'yes') ) # Default authorizations # role action arguments DEF_AUTHS = (('basketusers', 'usebaskets', {}), ('loanusers', 'useloans', {}), ('groupusers', 'usegroups', {}), ('alertusers', 'usealerts', {}), ('messageusers', 'usemessages', {}), ('holdingsusers', 'viewholdings', {}), ('statisticsusers', 'viewstatistics', {})) # Demo site authorizations # role action arguments DEF_DEMO_AUTHS = ( ('photocurator', 'runwebcoll', {'collection': 'Pictures'}), ('restrictedpicturesviewer', 'viewrestrdoc', {'status': 'restricted_picture'}), ('thesesviewer', VIEWRESTRCOLL, {'collection': 'Theses'}), ('referee_DEMOBOO_*', 'referee', {'doctype': 'DEMOBOO', 'categ': '*'}), ('curator', 'cfgbibknowledge', {}), ('curator', 'runbibedit', {}), ('curator', 'runbibeditmulti', {}), ('curator', 'runbibmerge', {}), ('swordcurator', 'runbibswordclient', {}), ('thesescurator', 'runbibedit', {'collection': 'Theses'}), ('thesescurator', VIEWRESTRCOLL, {'collection': 'Theses'}), ('photocurator', 'runbibedit', {'collection': 'Pictures'}), ('referee_DEMOBOO_*', 'runbibedit', {'collection': 'Books'}), ('submit_DEMOJRN_*', 'submit', {'doctype': 'DEMOJRN', 'act': 'SBI', 'categ': '*'}), ('submit_DEMOJRN_*', 'submit', {'doctype': 'DEMOJRN', 'act': 'MBI', 'categ': '*'}), ('submit_DEMOJRN_*', 'cfgwebjournal', {'name': 'AtlantisTimes', 'with_editor_rights': 'no'}), ('atlantiseditor', 'cfgwebjournal', {'name': 'AtlantisTimes', 'with_editor_rights': 'yes'}), ('referee_DEMOBOO_*', 'runbatchuploader', {'collection': 'Books'}), ('poetrycommentreader', 'viewcomment', {'collection': 'Poetry'}) ) _ = gettext_set_language(CFG_SITE_LANG) # Activities (i.e. actions) for which exists an administrative web interface. CFG_ACC_ACTIVITIES_URLS = { 'runbibedit' : (_("Run Record Editor"), "%s/record/edit/?ln=%%s" % CFG_SITE_URL), 'runbibeditmulti' : (_("Run Multi-Record Editor"), "%s/record/multiedit/?ln=%%s" % CFG_SITE_URL), 'runbibdocfile' : (_("Run Document File Manager"), "%s/submit/managedocfiles?ln=%%s" % CFG_SITE_URL), 'runbibmerge' : (_("Run Record Merger"), "%s/record/merge/?ln=%%s" % CFG_SITE_URL), 'runbibswordclient' : (_("Run BibSword client"), "%s/bibsword/?ln=%%s" % CFG_SITE_URL), 'cfgbibknowledge' : (_("Configure BibKnowledge"), "%s/kb?ln=%%s" % CFG_SITE_URL), 'cfgbibformat' : (_("Configure BibFormat"), "%s/admin/bibformat/bibformatadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgoaiharvest' : (_("Configure OAI Harvest"), "%s/admin/bibharvest/oaiharvestadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgoairepository' : (_("Configure OAI Repository"), "%s/admin/bibharvest/oairepositoryadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgbibindex' : (_("Configure BibIndex"), "%s/admin/bibindex/bibindexadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgbibrank' : (_("Configure BibRank"), "%s/admin/bibrank/bibrankadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgwebaccess' : (_("Configure WebAccess"), "%s/admin/webaccess/webaccessadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgwebcomment' : (_("Configure WebComment"), "%s/admin/webcomment/webcommentadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgwebsearch' : (_("Configure WebSearch"), "%s/admin/websearch/websearchadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgwebsubmit' : (_("Configure WebSubmit"), "%s/admin/websubmit/websubmitadmin.py?ln=%%s" % CFG_SITE_URL), 'cfgwebjournal' : (_("Configure WebJournal"), "%s/admin/webjournal/webjournaladmin.py?ln=%%s" % CFG_SITE_URL), 'runbibcirculation' : (_("Run BibCirculation"), "%s/admin/bibcirculation/bibcirculationadmin.py?ln=%%s" % CFG_SITE_URL), 'runbatchuploader' : (_("Run Batch Uploader"), "%s/batchuploader/metadata?ln=%%s" % CFG_SITE_URL) } CFG_WEBACCESS_MSGS = { 0: 'Try to login with another account.' % (CFG_SITE_SECURE_URL), 1: '
If you think this is not correct, please contact: %s' % (CFG_SITE_SUPPORT_EMAIL, CFG_SITE_SUPPORT_EMAIL), 2: '
If you have any questions, please write to %s' % (CFG_SITE_SUPPORT_EMAIL, CFG_SITE_SUPPORT_EMAIL), 3: 'Guest users are not allowed, please login.' % CFG_SITE_SECURE_URL, 4: 'The site is temporarily closed for maintenance. Please come back soon.', 5: 'Authorization failure', 6: '%s temporarily closed' % CFG_SITE_NAME, 7: 'This functionality is temporarily closed due to server maintenance. Please use only the search engine in the meantime.', 8: 'Functionality temporarily closed' } CFG_WEBACCESS_WARNING_MSGS = { 0: 'Authorization granted', 1: 'You are not authorized to perform this action.', 2: 'You are not authorized to perform any action.', 3: 'The action %s does not exist.', 4: 'Unexpected error occurred.', 5: 'Missing mandatory keyword argument(s) for this action.', 6: 'Guest accounts are not authorized to perform this action.', 7: 'Not enough arguments, user ID and action name required.', 8: 'Incorrect keyword argument(s) for this action.', 9: """Account '%s' is not yet activated.""", 10: """You were not authorized by the authentication method '%s'.""", 11: """The selected login method '%s' is not the default method for this account, please try another one.""", 12: """Selected login method '%s' does not exist.""", 13: """Could not register '%s' account.""", 14: """Could not login using '%s', because this user is unknown.""", 15: """Could not login using your '%s' account, because you have introduced a wrong password.""", 16: """External authentication troubles using '%s' (maybe temporary network problems).""", 17: """You have not yet confirmed the email address for the '%s' authentication method.""", 18: """The administrator has not yet activated your account for the '%s' authentication method.""", 19: """The site is having troubles in sending you an email for confirming your email address. The error has been logged and will be taken care of as soon as possible.""", 20: """No roles are authorized to perform action %s with the given parameters.""" } diff --git a/modules/webaccess/lib/access_control_engine.py b/modules/webaccess/lib/access_control_engine.py index 356962c49..ea5dc625f 100644 --- a/modules/webaccess/lib/access_control_engine.py +++ b/modules/webaccess/lib/access_control_engine.py @@ -1,148 +1,90 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Invenio Access Control Engine in mod_python.""" __revision__ = "$Id$" import cgi import sys from urllib import quote if sys.hexversion < 0x2040000: # pylint: disable=W0622 from sets import Set as set # pylint: enable=W0622 from invenio.config import CFG_SITE_SECURE_URL from invenio.dbquery import run_sql from invenio.access_control_admin import acc_find_possible_roles, acc_is_user_in_role, CFG_SUPERADMINROLE_ID, acc_get_role_users from invenio.access_control_config import CFG_WEBACCESS_WARNING_MSGS, CFG_WEBACCESS_MSGS from invenio.webuser import collect_user_info -from invenio.access_control_firerole import acc_firerole_suggest_apache_p, deserialize, load_role_definition, acc_firerole_extract_emails +from invenio.access_control_firerole import deserialize, load_role_definition, acc_firerole_extract_emails from invenio.urlutils import make_canonical_urlargd -CFG_CALLED_FROM_APACHE = 1 #1=web,0=cli -try: - import _apache -except ImportError, e: - CFG_CALLED_FROM_APACHE = 0 - -def make_list_apache_firerole(name_action, arguments): - """Given an action and a dictionary arguments returns a list of all the - roles (and their descriptions) which are authorized to perform this - action with these arguments, and whose FireRole definition expect - an Apache Password membership. - """ - roles = acc_find_possible_roles(name_action, **arguments) - - ret = [] - - for role in roles: - res = run_sql('SELECT name, description, firerole_def_ser FROM accROLE WHERE id=%s', (role, )) - if acc_firerole_suggest_apache_p(deserialize(res[0][2])): - ret.append((res[0][0], res[0][1])) - return ret - -def _format_list_of_apache_firerole(roles, referer): - """Given a list of tuples (role, description) (returned by make_list_apache_firerole), and a referer url, returns a nice string for - presenting urls that let the user login with Apache password through - Firerole. - This function is needed only at CERN for aiding in the migration of - Apache Passwords restricted collections to FireRole roles. - Please use it with care.""" - out = "" - if roles: - out += "

1) Here is a list of administrative roles you may have " \ - "received authorization for via an Apache password. If you are aware " \ - "of such a password, please follow the corresponding link:" - out += "" - for name, description in roles: - out += "" - out += "" % \ - ('%s%s' % (CFG_SITE_SECURE_URL, make_canonical_urlargd({'realm' : name, 'referer' : referer}, {})), name, description) - out += "" - out += "
%s - %s
" - out += "

" - return out - -def make_apache_message(name_action, arguments, referer=None): - """Given an action name and a dictionary of arguments and a refere url - it returns a a nice string for presenting urls that let the user login - with Apache password through Firerole authorized roles. - This function is needed only at CERN for aiding in the migration of - Apache Passwords restricted collections to FireRole roles. - Please use it with care.""" - if not referer: - referer = '%s/youraccount/youradminactivities' % CFG_SITE_SECURE_URL - roles = make_list_apache_firerole(name_action, arguments) - if roles: - return _format_list_of_apache_firerole(roles, referer) - else: - return "" - def acc_authorize_action(req, name_action, authorized_if_no_roles=False, **arguments): """ Given the request object (or the user_info dictionary, or the uid), checks if the user is allowed to run name_action with the given parameters. If authorized_if_no_roles is True and no role exists (different than superadmin) that are authorized to execute the given action, the authorization will be granted. Returns (0, msg) when the authorization is granted, (1, msg) when it's not. """ user_info = collect_user_info(req) roles = acc_find_possible_roles(name_action, always_add_superadmin=False, **arguments) for id_role in roles: if acc_is_user_in_role(user_info, id_role): ## User belong to at least one authorized role. return (0, CFG_WEBACCESS_WARNING_MSGS[0]) if acc_is_user_in_role(user_info, CFG_SUPERADMINROLE_ID): ## User is SUPERADMIN return (0, CFG_WEBACCESS_WARNING_MSGS[0]) if not roles: ## No role is authorized for the given action/arguments if authorized_if_no_roles: ## User is authorized because no authorization exists for the given ## action/arguments return (0, CFG_WEBACCESS_WARNING_MSGS[0]) else: ## User is not authorized. return (20, CFG_WEBACCESS_WARNING_MSGS[20] % cgi.escape(name_action)) ## User is not authorized - return (1, "%s %s %s" % (CFG_WEBACCESS_WARNING_MSGS[1], (CFG_CALLED_FROM_APACHE and "%s %s" % (CFG_WEBACCESS_MSGS[0] % quote(user_info['uri']), CFG_WEBACCESS_MSGS[1]) or ""), make_apache_message(name_action, arguments, user_info['uri']))) + in_a_web_request_p = bool(user_info['uri']) + return (1, "%s %s" % (CFG_WEBACCESS_WARNING_MSGS[1], (in_a_web_request_p and "%s %s" % (CFG_WEBACCESS_MSGS[0] % quote(user_info['uri']), CFG_WEBACCESS_MSGS[1]) or ""))) def acc_get_authorized_emails(name_action, **arguments): """ Given the action and its arguments, try to retireve all the matching email addresses of users authorized. This is a best effort operation, because if a role is authorized and happens to be defined using a FireRole rule based on regular expression or on IP addresses, non every email might be returned. @param name_action: the name of the action. @type name_action: string @param arguments: the arguments to the action. @return: the list of authorized emails. @rtype: set of string """ authorized_emails = set() roles = acc_find_possible_roles(name_action, always_add_superadmin=False, **arguments) for id_role in roles: for dummy1, email, dummy2 in acc_get_role_users(id_role): authorized_emails.add(email.lower().strip()) firerole = load_role_definition(id_role) authorized_emails.union(acc_firerole_extract_emails(firerole)) return authorized_emails diff --git a/modules/webaccess/lib/access_control_firerole.py b/modules/webaccess/lib/access_control_firerole.py index 1344dbc3a..098cce844 100644 --- a/modules/webaccess/lib/access_control_firerole.py +++ b/modules/webaccess/lib/access_control_firerole.py @@ -1,348 +1,336 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Invenio Access Control FireRole.""" __revision__ = "$Id$" __lastupdated__ = """$Date$""" """These functions are for realizing a firewall like role definition for extending webaccess to connect user to roles using every infos about users. """ import re import cPickle from zlib import compress, decompress import sys import time if sys.hexversion < 0x2040000: # pylint: disable=W0622 from sets import Set as set # pylint: enable=W0622 from invenio.access_control_config import InvenioWebAccessFireroleError from invenio.dbquery import run_sql, blob_to_string from invenio.config import CFG_CERN_SITE from invenio.access_control_config import CFG_ACC_EMPTY_ROLE_DEFINITION_SRC, \ CFG_ACC_EMPTY_ROLE_DEFINITION_SER, CFG_ACC_EMPTY_ROLE_DEFINITION_OBJ # INTERFACE def compile_role_definition(firerole_def_src): """ Given a text in which every row contains a rule it returns the compiled object definition. Rules have the following syntax: allow|deny [not] field {list of one or more (double)quoted string or regexp} or allow|deny any Every row may contain a # sign followed by a comment which are discarded. Field could be any key contained in a user_info dictionary. If the key does not exist in the dictionary, the rule is skipped. The first rule which matches return. """ line = 0 ret = [] default_allow_p = False - suggest_apache_p = False if not firerole_def_src or not firerole_def_src.strip(): firerole_def_src = CFG_ACC_EMPTY_ROLE_DEFINITION_SRC for row in firerole_def_src.split('\n'): line += 1 row = row.strip() if not row: continue clean_row = _no_comment_re.sub('', row) if clean_row: g = _any_rule_re.match(clean_row) if g: default_allow_p = g.group('command').lower() == 'allow' break g = _rule_re.match(clean_row) if g: allow_p = g.group('command').lower() == 'allow' not_p = g.group('not') != None field = g.group('field').lower() - # Renaming groups to group and apache_groups to apache_group + # Renaming groups to group for alias_item in _aliasTable: if field in alias_item: field = alias_item[0] break if field.startswith('precached_'): raise InvenioWebAccessFireroleError("Error while compiling rule %s (line %s): %s is a reserved key and can not be used in FireRole rules!" % (row, line, field)) expressions = g.group('expression')+g.group('more_expressions') expressions_list = [] for expr in _expressions_re.finditer(expressions): expr = expr.group() if field in ('from', 'until'): try: expressions_list.append((False, time.mktime(time.strptime(expr[1:-1], '%Y-%m-%d')))) except Exception, msg: raise InvenioWebAccessFireroleError("Syntax error while compiling rule %s (line %s): %s is not a valid date with format YYYY-MM-DD because %s!" % (row, line, expr, msg)) elif expr[0] == '/': try: expressions_list.append((True, re.compile(expr[1:-1], re.I))) except Exception, msg: raise InvenioWebAccessFireroleError("Syntax error while compiling rule %s (line %s): %s is not a valid re because %s!" % (row, line, expr, msg)) else: if field == 'remote_ip' and '/' in expr[1:-1]: try: expressions_list.append((False, _ip_matcher_builder(expr[1:-1]))) except Exception, msg: raise InvenioWebAccessFireroleError("Syntax error while compiling rule %s (line %s): %s is not a valid ip group because %s!" % (row, line, expr, msg)) else: expressions_list.append((False, expr[1:-1])) expressions_list = tuple(expressions_list) if field in ('from', 'until'): if len(expressions_list) != 1: raise InvenioWebAccessFireroleError("Error when compiling rule %s (line %s): exactly one date is expected when using 'from' or 'until', but %s were found" % (row, line, len(expressions_list))) if not_p: raise InvenioWebAccessFireroleError("Error when compiling rule %s (line %s): 'not' is not allowed when using 'from' or 'until'" % (row, line)) - elif field in ('apache_group', 'apache_user'): - suggest_apache_p = True ret.append((allow_p, not_p, field, expressions_list)) else: raise InvenioWebAccessFireroleError("Syntax error while compiling rule %s (line %s): not a valid rule!" % (row, line)) - return (default_allow_p, suggest_apache_p, tuple(ret)) + return (default_allow_p, tuple(ret)) def repair_role_definitions(): """ Try to rebuild compiled serialized definitions from their respectives sources. This is needed in case Python break back compatibility. """ definitions = run_sql("SELECT id, firerole_def_src FROM accROLE") for role_id, firerole_def_src in definitions: run_sql("UPDATE accROLE SET firerole_def_ser=%s WHERE id=%s", (serialize(compile_role_definition(firerole_def_src)), role_id)) def store_role_definition(role_id, firerole_def_ser, firerole_def_src): """ Store a compiled serialized definition and its source in the database alongside the role to which it belong. @param role_id: the role_id @param firerole_def_ser: the serialized compiled definition @param firerole_def_src: the sources from which the definition was taken """ run_sql("UPDATE accROLE SET firerole_def_ser=%s, firerole_def_src=%s WHERE id=%s", (firerole_def_ser, firerole_def_src, role_id)) def load_role_definition(role_id): """ Load the definition corresponding to a role. If the compiled definition is corrupted it try to repairs definitions from their sources and try again to return the definition. @param role_id: @return: a deserialized compiled role definition """ res = run_sql("SELECT firerole_def_ser FROM accROLE WHERE id=%s", (role_id, ), 1) if res: try: return deserialize(res[0][0]) except Exception: ## Something bad might have happened? (Update of Python?) repair_role_definitions() res = run_sql("SELECT firerole_def_ser FROM accROLE WHERE id=%s", (role_id, ), 1) if res: return deserialize(res[0][0]) return CFG_ACC_EMPTY_ROLE_DEFINITION_OBJ -def acc_firerole_suggest_apache_p(firerole_def_obj): - """Return True if the given firerole definition suggest the authentication - through Apache.""" - try: - default_allow_p, suggest_apache_p, rules = firerole_def_obj - return suggest_apache_p - except Exception, msg: - raise InvenioWebAccessFireroleError, msg - def acc_firerole_extract_emails(firerole_def_obj): """ Best effort function to extract all the possible email addresses authorized by the given firerole. """ authorized_emails = set() try: - default_allow_p, suggest_apache_p, rules = firerole_def_obj + default_allow_p, rules = firerole_def_obj for (allow_p, not_p, field, expressions_list) in rules: # for every rule if not_p: continue if field == 'group': for reg_p, expr in expressions_list: if reg_p: continue if CFG_CERN_SITE and expr.endswith(' [CERN]'): authorized_emails.add(expr[:len(' [CERN]')].lower().strip() + '@cern.ch') emails = run_sql("SELECT user.email FROM usergroup JOIN user_usergroup ON usergroup.id=user_usergroup.id_usergroup JOIN user ON user.id=user_usergroup.id_user WHERE usergroup.name=%s", (expr, )) for email in emails: authorized_emails.add(email[0].lower().strip()) elif field == 'email': for reg_p, expr in expressions_list: if reg_p: continue authorized_emails.add(expr.lower().strip()) elif field == 'uid': for reg_p, expr in expressions_list: if reg_p: continue email = run_sql("SELECT email FROM user WHERE id=%s", (expr, )) if email: authorized_emails.add(email[0][0].lower().strip()) return authorized_emails except Exception, msg: raise InvenioWebAccessFireroleError, msg def acc_firerole_check_user(user_info, firerole_def_obj): """ Given a user_info dictionary, it matches the rules inside the deserializez compiled definition in order to discover if the current user match the roles corresponding to this definition. @param user_info: a dict produced by collect_user_info which contains every info about a user @param firerole_def_obj: a compiled deserialized definition produced by compile_role_defintion @return: True if the user match the definition, False otherwise. """ try: - default_allow_p, suggest_apache_p, rules = firerole_def_obj + default_allow_p, rules = firerole_def_obj for (allow_p, not_p, field, expressions_list) in rules: # for every rule - group_p = field in ('group', 'apache_group') # Is it related to group? + group_p = field == 'group' # Is it related to group? ip_p = field == 'remote_ip' # Is it related to Ips? until_p = field == 'until' # Is it related to dates? from_p = field == 'from' # Idem. next_expr_p = False # Silly flag to break 2 for cycles if not user_info.has_key(field) and not from_p and not until_p: continue for reg_p, expr in expressions_list: # For every element in the rule if group_p: # Special case: groups if reg_p: # When it is a regexp for group in user_info[field]: # iterate over every group if expr.match(group): # if it matches if not_p: # if must not match next_expr_p = True # let's skip to next expr break else: # Ok! return allow_p if next_expr_p: break # I said: let's skip to next rule ;-) elif expr.lower() in [group.lower() for group in user_info[field]]: # Simple expression then just check for expr in groups if not_p: # If expr is in groups then if must not match break # let's skip to next expr else: # Ok! return allow_p elif reg_p: # Not a group, then easier. If it's a regexp if expr.match(user_info[field]): # if it matches if not_p: # If must not match break # Let's skip to next expr else: return allow_p # Ok! elif ip_p and type(expr) == type(()): # If it's just a simple expression but an IP! if _ipmatch(user_info['remote_ip'], expr): # Then if Ip matches if not_p: # If must not match break # let's skip to next expr else: return allow_p # ok! elif until_p: if time.time() <= expr: if allow_p: break else: return False elif allow_p: return False else: break elif from_p: if time.time() >= expr: if allow_p: break else: return False elif allow_p: return False else: break elif expr.lower() == user_info[field].lower(): # Finally the easiest one!! if not_p: # ... break else: # ... return allow_p # ... if not_p and not next_expr_p: # Nothing has matched and we got not return allow_p # Then the whole rule matched! except Exception, msg: raise InvenioWebAccessFireroleError, msg return default_allow_p # By default we allow ;-) it'an OpenAccess project def serialize(firerole_def_obj): """ Serialize and compress a definition.""" if firerole_def_obj == CFG_ACC_EMPTY_ROLE_DEFINITION_OBJ: return CFG_ACC_EMPTY_ROLE_DEFINITION_SER elif firerole_def_obj: return compress(cPickle.dumps(firerole_def_obj, -1)) else: return CFG_ACC_EMPTY_ROLE_DEFINITION_SER def deserialize(firerole_def_ser): """ Deserialize and decompress a definition.""" if firerole_def_ser: return cPickle.loads(decompress(blob_to_string(firerole_def_ser))) else: return CFG_ACC_EMPTY_ROLE_DEFINITION_OBJ # IMPLEMENTATION # Comment finder _no_comment_re = re.compile(r'[\s]*(?allow|deny)[\s]+(?:(?Pnot)[\s]+)?(?P[\w]+)[\s]+(?P(?([\s]*,[\s]*((?allow|deny)[\s]+(any|all)[\s]*', re.I) # Sub expression finder _expressions_re = re.compile(r'(? 123.2.12.12 + """ + try: + return '.'.join(str(int(number)) for number in ip.split('.')) + except ValueError: + ## e.g. if it's IPV6 ::1 + return ip + +def load_robot_keys(): + """ + @return: the robot key dictionary. + """ + from cPickle import loads + from zlib import decompress + try: + robot_keys = loads(decompress(open(CFG_ROBOT_KEYS_PATH).read())) + if not isinstance(robot_keys, dict): + return {} + else: + return robot_keys + except: + return {} + +class ExternalAuthRobot(ExternalAuth): + """ + This class implement an external authentication method suitable to be + used by an external service that, after having authenticated a user, + will provide a URL to the user that, once followed, will successfully + login the user into Invenio, with any detail the external service + decided to provide to the Invenio installation. + + Such URL should be built as follows: + BASE?QUERY + + where BASE is CFG_SITE_SECURE_URL/youraccount/robotlogin + + and QUERY is a urlencoded mapping of the following key->values: + - assertion: an assertion, i.e. a piece of information describing the + user, see below for more details. + - robot: the identifier of the external service providing the assertion + - login_method: the name of the login method as defined in CFG_EXTERNAL_AUTHENTICATION. + - digest: the digest of the signature as detailed below. + - referer: the URL where the user should be redirected after successful + login (it is called referer as, for historical reasons, this is the + original URL of the page on which, a human-user has clicked "login". + + the "assertion" should be a JSON serialized mapping with the following + keys: + - email: the email of the user (i.e. its identifier). + - nickname: optional nickname of the user. + - groups: an optional ';'-separated list of groups to which the user + belongs to. + - __timeout__: the number of seconds (floating point) from the Epoch, + after which the URL will no longer be valid. (expressed in UTC) + - __userip__: the IP address of the user for whom this URL has been + created. (if the user will follow this URL using a different URL the + request will not be valid) + - any other key can be added and will be merged in the external user + settings. + + If L{use_zlib} is True the assertion is a base64-url-flavour encoding + of the zlib compression of the original assertion (useful for shortening + the URL while make it easy to type). + + The "digest" is the hexadecimal representation of the digest using the + HMAC-SHA1 method to sign the assertion with the secret key associated + with the robot for the given login_method. + + @param enforce_external_nicknames: whether to trust nicknames provided by + the external service and use them (if possible) as unique identifier + in the system. + @type enforce_external_nicknames: boolean + @param email_attribute_name: the actual key in the assertion that will + contain the email. + @type email_attribute_name: string + @param nickname_attribute_name: the actual key in the assertion that will + contain the nickname. + @type nickname_attribute_name: string + @param groups_attribute_name: the actual key in the assertion that will + contain the groups. + @type groups_attribute_name: string + @param groups_separator: the string used to separate groups. + @type groups_separator: string + @param timeout_attribute_name: the actual key in the assertion that will + contain the timeout. + @type timeout_attribute_name: string + @param userip_attribute_name: the actual key in the assertion that will + contain the user IP. + @type userip_attribute_name: string + @param check_user_ip: whether to check for the IP address of the user + using the given URL, against the IP address stored in the assertion + to be identical. + @type check_user_ip: boolean + @param use_zlib: whether to use base64-url-flavour encoding of the zlib + compression of the json serialization of the assertion or simply + the json serialization of the assertion. + @type use_zlib: boolean + """ + def __init__(self, enforce_external_nicknames=False, + email_attribute_name=CFG_ROBOT_EMAIL_ATTRIBUTE_NAME, + nickname_attribute_name=CFG_ROBOT_NICKNAME_ATTRIBUTE_NAME, + groups_attribute_name=CFG_ROBOT_GROUPS_ATTRIBUTE_NAME, + groups_separator=CFG_ROBOT_GROUPS_SEPARATOR, + timeout_attribute_name=CFG_ROBOT_TIMEOUT_ATTRIBUTE_NAME, + userip_attribute_name=CFG_ROBOT_USERIP_ATTRIBUTE_NAME, + check_user_ip=True, + use_zlib=True, + ): + ExternalAuth.__init__(self, enforce_external_nicknames=enforce_external_nicknames) + self.email_attribute_name = email_attribute_name + self.nickname_attribute_name = nickname_attribute_name + self.groups_attribute_name = groups_attribute_name + self.groups_separator = groups_separator + self.timeout_attribute_name = timeout_attribute_name + self.userip_attribute_name = userip_attribute_name + self.check_user_ip = check_user_ip + self.use_zlib = use_zlib + + def __extract_attribute(self, req): + """ + Load from the request the given assertion, extract all the attribute + to properly login the user, and verify that the data are actually + both well formed and signed correctly. + """ + from invenio.bibedit_utils import json_unicode_to_utf8 + from invenio.webinterface_handler import wash_urlargd + args = wash_urlargd(req.form, { + 'assertion': (str, ''), + 'robot': (str, ''), + 'digest': (str, ''), + 'login_method': (str, '')}) + assertion = args['assertion'] + digest = args['digest'] + robot = args['robot'] + login_method = args['login_method'] + shared_key = load_robot_keys().get(login_method, {}).get(robot) + if shared_key is None: + raise InvenioWebAccessExternalAuthError("A key does not exist for robot: %s, login_method: %s" % (robot, login_method)) + if not self.verify(shared_key, assertion, digest): + raise InvenioWebAccessExternalAuthError("The provided assertion does not validate against the digest %s for robot %s" % (repr(digest), repr(robot))) + if self.use_zlib: + try: + assertion = decompress(base64.urlsafe_b64decode(assertion)) + except: + raise InvenioWebAccessExternalAuthError("The provided assertion is corrupted") + data = json_unicode_to_utf8(json.loads(assertion)) + if not isinstance(data, dict): + raise InvenioWebAccessExternalAuthError("The provided assertion is invalid") + timeout = data[self.timeout_attribute_name] + if timeout < time.time(): + raise InvenioWebAccessExternalAuthError("The provided assertion is expired") + userip = data.get(self.userip_attribute_name) + if not self.check_user_ip or (normalize_ip(userip) == normalize_ip(req.remote_ip)): + return data + else: + raise InvenioWebAccessExternalAuthError("The provided assertion has been issued for a different IP address (%s instead of %s)" % (userip, req.remote_ip)) + + def auth_user(self, username, password, req=None): + """Authenticate user-supplied USERNAME and PASSWORD. Return + None if authentication failed, or the email address of the + person if the authentication was successful. In order to do + this you may perhaps have to keep a translation table between + usernames and email addresses. + Raise InvenioWebAccessExternalAuthError in case of external troubles. + """ + data = self.__extract_attribute(req) + email = data.get(self.email_attribute_name) + if email: + if isinstance(email, str): + return email.strip().lower() + else: + raise InvenioWebAccessExternalAuthError("The email provided in the assertion is invalid: %s" % (repr(email))) + else: + return None + + def fetch_user_groups_membership(self, username, password=None, req=None): + """Given a username and a password, returns a dictionary of groups + and their description to which the user is subscribed. + Raise InvenioWebAccessExternalAuthError in case of troubles. + """ + if self.groups_attribute_name: + data = self.__extract_attribute(req) + groups = data.get(self.groups_attribute_name) + if groups: + if isinstance(groups, str): + groups = [group.strip() for group in groups.split(self.groups_separator)] + return dict(zip(groups, groups)) + else: + raise InvenioWebAccessExternalAuthError("The groups provided in the assertion are invalid: %s" % (repr(groups))) + return {} + + def fetch_user_nickname(self, username, password=None, req=None): + """Given a username and a password, returns the right nickname belonging + to that user (username could be an email). + """ + if self.nickname_attribute_name: + data = self.__extract_attribute(req) + nickname = data.get(self.nickname_attribute_name) + if nickname: + if isinstance(nickname, str): + return nickname.strip().lower() + else: + raise InvenioWebAccessExternalAuthError("The nickname provided in the assertion is invalid: %s" % (repr(nickname))) + return None + + def fetch_user_preferences(self, username, password=None, req=None): + """Given a username and a password, returns a dictionary of keys and + values, corresponding to external infos and settings. + + userprefs = {"telephone": "2392489", + "address": "10th Downing Street"} + + (WEBUSER WILL erase all prefs that starts by EXTERNAL_ and will + store: "EXTERNAL_telephone"; all internal preferences can use whatever + name but starting with EXTERNAL). If a pref begins with HIDDEN_ it will + be ignored. + """ + data = self.__extract_attribute(req) + for key in (self.email_attribute_name, self.groups_attribute_name, self.nickname_attribute_name, self.timeout_attribute_name, self.userip_attribute_name): + if key and key in data: + del data[key] + return data + + def robot_login_method_p(): + """Return True if this method is dedicated to robots and should + not therefore be available as a choice to regular users upon login. + """ + return True + robot_login_method_p = staticmethod(robot_login_method_p) + + def sign(secret, assertion): + """ + @return: a signature of the given assertion. + @rtype: string + @note: override this method if you want to change the signature + algorithm (e.g. to use GPG). + @see: L{verify} + """ + return hmac.new(secret, assertion, sha1).hexdigest() + sign = staticmethod(sign) + + def verify(secret, assertion, signature): + """ + @return: True if the signature is valid + @rtype: boolean + @note: override this method if you want to change the signature + algorithm (e.g. to use GPG) + @see: L{sign} + """ + return hmac.new(secret, assertion, sha1).hexdigest() == signature + verify = staticmethod(verify) + + def test_create_example_url(self, email, login_method, robot, ip, assertion=None, timeout=None, referer=None, groups=None, nickname=None): + """ + Create a test URL to test the robot login. + + @param email: email of the user we want to login as. + @type email: string + @param login_method: the login_method name as specified in CFG_EXTERNAL_AUTHENTICATION. + @type login_method: string + @param robot: the identifier of this robot. + @type robot: string + @param assertion: any further data we want to send to. + @type: json serializable mapping + @param ip: the IP of the user. + @type: string + @param timeout: timeout when the URL will expire (in seconds from the Epoch) + @type timeout: float + @param referer: the URL where to land after successful login. + @type referer: string + @param groups: the list of optional group of the user. + @type groups: list of string + @param nickname: the optional nickname of the user. + @type nickname: string + @return: the URL to login as the user. + @rtype: string + """ + from invenio.access_control_config import CFG_EXTERNAL_AUTHENTICATION + from invenio.urlutils import create_url + if assertion is None: + assertion = {} + assertion[self.email_attribute_name] = email + if nickname: + assertion[self.nickname_attribute_name] = nickname + if groups: + assertion[self.groups_attribute_name] = self.groups_separator.join(groups) + if timeout is None: + timeout = time.time() + CFG_ROBOT_URL_TIMEOUT + assertion[self.timeout_attribute_name] = timeout + if referer is None: + referer = CFG_SITE_URL + if login_method is None: + for a_login_method, details in CFG_EXTERNAL_AUTHENTICATION.iteritems(): + if details[2]: + login_method = a_login_method + break + robot_keys = load_robot_keys() + assertion[self.userip_attribute_name] = ip + assertion = json.dumps(assertion) + if self.use_zlib: + assertion = base64.urlsafe_b64encode(compress(assertion)) + shared_key = robot_keys[login_method][robot] + digest = self.sign(shared_key, assertion) + return create_url("%s%s" % (CFG_SITE_SECURE_URL, "/youraccount/robotlogin"), { + 'assertion': assertion, + 'robot': robot, + 'login_method': login_method, + 'digest': digest, + 'referer': referer}) + +def update_robot_key(login_method, robot, key=None): + """ + Utility to update the robot key store. + @param login_method: the login_method name as per L{CFG_EXTERNAL_AUTHENTICATION}. + It should correspond to a robot-enable login method. + @type: string + @param robot: the robot identifier + @type robot: string + @param key: the secret + @type key: string + @note: if the secret is empty the corresponding key will be removed. + """ + from invenio.websearch_webcoll import mymkdir + robot_keys = load_robot_keys() + if key is None and login_method in robot_keys and robot in robot_keys[login_method]: + del robot_keys[login_method][robot] + if not robot_keys[login_method]: + del robot_keys[login_method] + else: + if login_method not in robot_keys: + robot_keys[login_method] = {} + robot_keys[login_method][robot] = key + mymkdir(os.path.join(CFG_ETCDIR, 'webaccess')) + open(CFG_ROBOT_KEYS_PATH, 'w').write(compress(dumps(robot_keys, -1))) + diff --git a/modules/webaccess/lib/webaccess_regression_tests.py b/modules/webaccess/lib/webaccess_regression_tests.py index 8b0663efd..83f05aade 100644 --- a/modules/webaccess/lib/webaccess_regression_tests.py +++ b/modules/webaccess/lib/webaccess_regression_tests.py @@ -1,111 +1,241 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """WebAccess Regression Test Suite.""" __revision__ = "$Id$" import unittest +import socket +import time +import cgi + +from urlparse import urlparse, urlunparse +from urllib import urlopen, urlencode from invenio.access_control_admin import acc_add_role, acc_delete_role, \ acc_get_role_definition from invenio.access_control_firerole import compile_role_definition, \ serialize, deserialize -from invenio.config import CFG_SITE_URL, CFG_SITE_SECURE_URL +from invenio.config import CFG_SITE_URL, CFG_SITE_SECURE_URL, CFG_DEVEL_SITE from invenio.testutils import make_test_suite, run_test_suite, \ test_web_page_content, merge_error_messages class WebAccessWebPagesAvailabilityTest(unittest.TestCase): """Check WebAccess web pages whether they are up or not.""" def test_webaccess_admin_interface_availability(self): """webaccess - availability of WebAccess Admin interface pages""" baseurl = CFG_SITE_URL + '/admin/webaccess/webaccessadmin.py/' _exports = ['', 'delegate_startarea', 'manageaccounts'] error_messages = [] for url in [baseurl + page for page in _exports]: # first try as guest: error_messages.extend(test_web_page_content(url, username='guest', expected_text= 'Authorization failure')) # then try as admin: error_messages.extend(test_web_page_content(url, username='admin')) if error_messages: self.fail(merge_error_messages(error_messages)) return def test_webaccess_admin_guide_availability(self): """webaccess - availability of WebAccess Admin guide pages""" url = CFG_SITE_URL + '/help/admin/webaccess-admin-guide' error_messages = test_web_page_content(url, expected_text="WebAccess Admin Guide") if error_messages: self.fail(merge_error_messages(error_messages)) return class WebAccessFireRoleTest(unittest.TestCase): """Check WebAccess behaviour WRT FireRole.""" def setUp(self): """Create a fake role.""" self.role_name = 'test' self.role_description = 'test role' self.role_definition = 'allow email /.*@cern.ch/' self.role_id, dummy, dummy, dummy = acc_add_role(self.role_name, self.role_description, serialize(compile_role_definition(self.role_definition)), self.role_definition) def tearDown(self): """Drop the fake role.""" acc_delete_role(self.role_id) def test_webaccess_firerole_serialization(self): """webaccess - firerole role definition correctly serialized""" def_ser = compile_role_definition(self.role_definition) tmp_def_ser = acc_get_role_definition(self.role_id) self.assertEqual(def_ser, deserialize(tmp_def_ser)) class WebAccessUseBasketsTest(unittest.TestCase): """ Check WebAccess behaviour WRT enabling/disabling web modules such as baskets. """ def test_precached_area_authorization(self): """webaccess - login-time precached authorizations for usebaskets""" error_messages = test_web_page_content(CFG_SITE_SECURE_URL + '/youraccount/display?ln=en', username='jekyll', password='j123ekyll', expected_text='Your Baskets') error_messages.extend(test_web_page_content(CFG_SITE_SECURE_URL + '/youraccount/display?ln=en', username='hyde', password='h123yde', unexpected_text='Your Baskets')) if error_messages: self.fail(merge_error_messages(error_messages)) - -TEST_SUITE = make_test_suite(WebAccessWebPagesAvailabilityTest, - WebAccessFireRoleTest, - WebAccessUseBasketsTest) +if CFG_DEVEL_SITE: + class WebAccessRobotLoginTest(unittest.TestCase): + """ + Check whether robot login functionality is OK. + """ + def _erase_example_user_and_groups(self): + from invenio.dbquery import run_sql + uid = run_sql("SELECT id FROM user WHERE email=%s", (self.a_email, )) + if uid: + run_sql("DELETE FROM user WHERE id=%s", (uid[0][0], )) + run_sql("DELETE FROM user_usergroup WHERE id_user=%s", (uid[0][0], )) + for method_name in self.robot_login_methods: + for group in self.some_groups: + run_sql("DELETE FROM usergroup WHERE name=%s", ("%s [%s]" % (group, method_name), )) + + def setUp(self): + from invenio.access_control_config import CFG_EXTERNAL_AUTHENTICATION + self.robot_login_methods = dict([(method_name, CFG_EXTERNAL_AUTHENTICATION[method_name]) for method_name in CFG_EXTERNAL_AUTHENTICATION if CFG_EXTERNAL_AUTHENTICATION[method_name] and CFG_EXTERNAL_AUTHENTICATION[method_name].robot_login_method_p()]) + self.a_robot = "regression-test" + self.a_password = "123" + self.a_email = "foo.bar@example.org" + self.a_nickname = "foo-bar" + self.some_groups = ["a group for regression test", "another group for regression test"] + self.myip = urlopen(CFG_SITE_URL + "/httptest/whatismyip").read() + from invenio.external_authentication_robot import update_robot_key + for method_name in self.robot_login_methods: + update_robot_key(method_name, self.a_robot, self.a_password) + from invenio.external_authentication_robot import load_robot_keys + + def tearDown(self): + from invenio.external_authentication_robot import update_robot_key + #for method_name in self.robot_login_methods: + #update_robot_key(method_name, self.a_robot) + from invenio.external_authentication_robot import load_robot_keys + self._erase_example_user_and_groups() + + def test_normal_robot_login_method(self): + """webaccess - robot login method""" + for method_name, method in self.robot_login_methods.iteritems(): + url = method.test_create_example_url(self.a_email, method_name, self.a_robot, self.myip) + try: + error_messages = test_web_page_content(url, expected_text=self.a_email) + if error_messages: + self.fail(merge_error_messages(error_messages)) + finally: + self._erase_example_user_and_groups() + + def test_robot_login_method_with_nickname(self): + """webaccess - robot login method with nickname""" + for method_name, method in self.robot_login_methods.iteritems(): + if method.enforce_external_nicknames: + url = method.test_create_example_url(self.a_email, method_name, self.a_robot, self.myip, nickname=self.a_nickname) + try: + error_messages = test_web_page_content(url, expected_text=self.a_nickname) + if error_messages: + self.fail(merge_error_messages(error_messages)) + finally: + self._erase_example_user_and_groups() + + def test_robot_login_method_with_groups(self): + """webaccess - robot login method with groups""" + for method_name, method in self.robot_login_methods.iteritems(): + url = method.test_create_example_url(self.a_email, method_name, self.a_robot, self.myip, groups=self.some_groups, referer=CFG_SITE_SECURE_URL + "/yourgroups/display") + try: + for group in self.some_groups: + error_messages = test_web_page_content(url, expected_text="%s [%s]" % (group, method_name)) + if error_messages: + self.fail(merge_error_messages(error_messages)) + finally: + self._erase_example_user_and_groups() + + def test_robot_login_method_wrong_ip(self): + """webaccess - robot login method wrong IP""" + for method_name, method in self.robot_login_methods.iteritems(): + url = method.test_create_example_url(self.a_email, method_name, self.a_robot, '123.123.123.123') + try: + error_messages = test_web_page_content(url, expected_text="The provided assertion has been issued for a different IP address") + if error_messages: + self.fail(merge_error_messages(error_messages)) + finally: + self._erase_example_user_and_groups() + + def test_robot_login_method_expired_assertion(self): + """webaccess - robot login method with expired assertion""" + for method_name, method in self.robot_login_methods.iteritems(): + url = method.test_create_example_url(self.a_email, method_name, self.a_robot, self.myip, timeout=time.time()) + time.sleep(1) + try: + error_messages = test_web_page_content(url, expected_text="The provided assertion is expired") + if error_messages: + self.fail(merge_error_messages(error_messages)) + finally: + self._erase_example_user_and_groups() + + def test_robot_login_method_with_invalid_signature(self): + """webaccess - robot login method with invalid signature""" + for method_name, method in self.robot_login_methods.iteritems(): + url = method.test_create_example_url(self.a_email, method_name, self.a_robot, self.myip) + url = list(urlparse(url)) + query = cgi.parse_qs(url[4]) + for key, value in query.items(): + query[key] = value[0] + digest = query['digest'] + digest0 = digest[0] + if digest0 == '0': + digest0 = '1' + else: + digest0 = '0' + digest = digest0 + digest[1:] + query['digest'] = digest + url[4] = urlencode(query) + url = urlunparse(url) + try: + error_messages = test_web_page_content(url, expected_text="does not validate against the digest") + if error_messages: + self.fail(merge_error_messages(error_messages)) + finally: + self._erase_example_user_and_groups() + + + TEST_SUITE = make_test_suite(WebAccessWebPagesAvailabilityTest, + WebAccessFireRoleTest, + WebAccessUseBasketsTest, + WebAccessRobotLoginTest) +else: + TEST_SUITE = make_test_suite(WebAccessWebPagesAvailabilityTest, + WebAccessFireRoleTest, + WebAccessUseBasketsTest) if __name__ == "__main__": run_test_suite(TEST_SUITE, warn_user=True) diff --git a/modules/webaccess/lib/webaccessadmin_lib.py b/modules/webaccess/lib/webaccessadmin_lib.py index 77a8d6fbb..5ffb74b3c 100644 --- a/modules/webaccess/lib/webaccessadmin_lib.py +++ b/modules/webaccess/lib/webaccessadmin_lib.py @@ -1,3648 +1,3837 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Invenio WebAccess Administrator Interface.""" __revision__ = "$Id$" __lastupdated__ = """$Date$""" ## fill config variables: import re import random import getopt import sys +import time from invenio.config import \ CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS, \ CFG_ACCESS_CONTROL_LEVEL_GUESTS, \ CFG_ACCESS_CONTROL_LEVEL_SITE, \ CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN, \ CFG_ACCESS_CONTROL_NOTIFY_ADMIN_ABOUT_NEW_ACCOUNTS, \ CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_ACTIVATION, \ CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_DELETION, \ CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT, \ CFG_SITE_LANG, \ CFG_SITE_NAME, \ CFG_SITE_SUPPORT_EMAIL, \ CFG_SITE_ADMIN_EMAIL, \ - CFG_SITE_URL + CFG_SITE_URL, \ + CFG_SITE_SECURE_URL import invenio.access_control_engine as acce import invenio.access_control_admin as acca from invenio.mailutils import send_email from invenio.bibrankadminlib import addadminbox, tupletotable, \ tupletotable_onlyselected, addcheckboxes, createhiddenform from invenio.access_control_firerole import compile_role_definition, \ repair_role_definitions, serialize from invenio.messages import gettext_set_language from invenio.dbquery import run_sql, OperationalError from invenio.webpage import page -from invenio.webuser import getUid, isGuestUser, page_not_authorized +from invenio.webuser import getUid, isGuestUser, page_not_authorized, collect_user_info from invenio.webuser import email_valid_p, get_user_preferences, \ set_user_preferences, update_Uid -from invenio.urlutils import redirect_to_url +from invenio.urlutils import redirect_to_url, wash_url_argument from invenio.access_control_config import DEF_DEMO_USER_ROLES, \ DEF_DEMO_ROLES, DEF_DEMO_AUTHS, WEBACCESSACTION, MAXPAGEUSERS, \ SUPERADMINROLE, CFG_EXTERNAL_AUTHENTICATION, DELEGATEADDUSERROLE, \ CFG_ACC_EMPTY_ROLE_DEFINITION_SRC, InvenioWebAccessFireroleError, \ - MAXSELECTUSERS + MAXSELECTUSERS, CFG_EXTERNAL_AUTH_DEFAULT from invenio.bibtask import authenticate from cgi import escape +## The following variable is True if the installation make any difference +## between HTTP Vs. HTTPS connections. +CFG_HAS_HTTPS_SUPPORT = CFG_SITE_URL != CFG_SITE_SECURE_URL def index(req, title='', body='', subtitle='', adminarea=2, authorized=0, ln=CFG_SITE_LANG): """main function to show pages for webaccessadmin. 1. if user not logged in and administrator, show the mustlogin page 2. if used without body argument, show the startpage 3. show admin page with title, body, subtitle and navtrail. - adminarea - number codes that tell what extra info to put in the navtrail - 0 - nothing extra - 1 - add Delegate Rights - 2 - add Manage WebAccess - maybe add: - 3: role admin - 4: action admin - 5: user area - 6: reset area - authorized - if 1, don't check if the user is allowed to be webadmin """ + if CFG_HAS_HTTPS_SUPPORT and not req.is_https(): + redirect_to_url(req, "%s/admin/webaccess/webaccessadmin.py" % CFG_SITE_SECURE_URL) navtrail_previous_links = 'Admin Area' \ - '' % (CFG_SITE_URL,) + '' % (CFG_SITE_SECURE_URL,) if body: if adminarea == 1: navtrail_previous_links += '> ' \ - 'Delegate Rights ' % (CFG_SITE_URL, ) + 'Delegate Rights ' % (CFG_SITE_SECURE_URL, ) if adminarea >= 2 and adminarea < 7: navtrail_previous_links += '> ' \ '' \ - 'WebAccess Admin ' % (CFG_SITE_URL, ) + 'WebAccess Admin ' % (CFG_SITE_SECURE_URL, ) if adminarea == 3: navtrail_previous_links += '> ' \ - 'Role Administration ' % (CFG_SITE_URL, ) + 'Role Administration ' % (CFG_SITE_SECURE_URL, ) elif adminarea == 4: navtrail_previous_links += '> ' \ 'Action Administration ' % (CFG_SITE_URL, ) + '/actionarea>Action Administration ' % (CFG_SITE_SECURE_URL, ) elif adminarea == 5: navtrail_previous_links += '> ' \ 'User Administration ' % (CFG_SITE_URL, ) + '/userarea>User Administration ' % (CFG_SITE_SECURE_URL, ) elif adminarea == 6: navtrail_previous_links += '> ' \ 'Reset Authorizations ' % (CFG_SITE_URL, ) + '/resetarea>Reset Authorizations ' % (CFG_SITE_SECURE_URL, ) elif adminarea == 7: navtrail_previous_links += '> ' \ 'Manage Accounts ' % (CFG_SITE_URL, ) + '/manageaccounts>Manage Accounts ' % (CFG_SITE_SECURE_URL, ) elif adminarea == 8: navtrail_previous_links += '> ' \ 'List Groups ' % (CFG_SITE_URL, ) + '/listgroups>List Groups ' % (CFG_SITE_SECURE_URL, ) + elif adminarea == 9: + navtrail_previous_links += '> ' \ + 'Manage Robot Login ' % (CFG_SITE_SECURE_URL, ) id_user = getUid(req) (auth_code, auth_message) = is_adminuser(req) if not authorized and auth_code != 0: return mustloginpage(req, auth_message) elif not body: title = 'WebAccess Admin' body = startpage() elif type(body) != str: body = addadminbox(subtitle, datalist=body) return page(title=title, uid=id_user, req=req, body=body, navtrail=navtrail_previous_links, lastupdated=__lastupdated__) def mustloginpage(req, message): """show a page asking the user to login.""" navtrail_previous_links = '' \ 'Admin Area > ' \ - 'WebAccess Admin ' % (CFG_SITE_URL, CFG_SITE_URL) + 'WebAccess Admin ' % (CFG_SITE_SECURE_URL, CFG_SITE_SECURE_URL) return page_not_authorized(req=req, text=message, navtrail=navtrail_previous_links) def is_adminuser(req): """check if user is a registered administrator. """ return acce.acc_authorize_action(req, WEBACCESSACTION) +def perform_managerobotlogin(req, robot_name='', new_pwd1='', new_pwd2='', login_method='', timeout='', referer='', ip='', action='', confirm=0, email='', groups='', nickname='', json_assertion='', url_only=0): + robot_name = wash_url_argument(robot_name, 'str') + new_pwd1 = wash_url_argument(new_pwd1, 'str') + new_pwd2 = wash_url_argument(new_pwd2, 'str') + login_method = wash_url_argument(login_method, 'str') + timeout = wash_url_argument(timeout, 'int') + referer = wash_url_argument(referer, 'str') + ip = wash_url_argument(ip, 'str') + action = wash_url_argument(action, 'str') + confirm = wash_url_argument(confirm, 'int') + email = wash_url_argument(email, 'str') + groups = wash_url_argument(groups, 'str') + nickname = wash_url_argument(nickname, 'str') + url_only = wash_url_argument(url_only, 'int') + json_assertion = wash_url_argument(json_assertion, 'str') + from invenio.external_authentication_robot import update_robot_key, load_robot_keys, json + (auth_code, auth_message) = acce.acc_authorize_action(req, 'cfgrobotkeys', login_method='*', robot='*') + if auth_code != 0: return mustloginpage(req, auth_message) + + available_robot_login_methods = [name for (name, method) in CFG_EXTERNAL_AUTHENTICATION.iteritems() if method and method.robot_login_method_p()] + + errors = [] + warnings = [] + messages = [] + if not available_robot_login_methods: + errors.append(""" +You should enable at least on robot based login method in access_control_config.py in the variable CFG_EXTERNAL_AUTHENTICATION. +""") + forms = "" + else: + robot_keys = load_robot_keys() + if not login_method: + login_method = available_robot_login_methods[0] + if not timeout: + timeout = 60 * 60 + if not ip: + ip = req.remote_ip + user_info = collect_user_info(req) + if not email: + email = user_info['email'] + if not nickname: + nickname = user_info['nickname'] + if not robot_name: + if login_method in robot_keys and robot_keys[login_method]: + robot_name = robot_keys[login_method].keys()[0] + if not referer: + referer = CFG_SITE_SECURE_URL + if action == 'changepwd': + if acce.acc_authorize_action(user_info, 'cfgrobotkeys', login_method=login_method, robot=robot_name)[0]: + errors.append("""You don't have proper authorization to modify robot %s for login_method %s.""" % (escape(robot_name), escape(login_method))) + if login_method not in available_robot_login_methods: + errors.append("""The login method must be one among the available_robot_login_methods (%s).""" % escape(', '.join(available_robot_login_methods))) + if new_pwd1 != new_pwd2: + errors.append("""The two passwords are not equal.""") + new_pwd1 = '' + new_pwd2 = '' + if not robot_name: + errors.append("""The robot name must be specified.""") + if int(confirm) == 1: + if not errors: + update_robot_key(login_method, robot_name, new_pwd1) + robot_keys = load_robot_keys() + if new_pwd1: + messages.append("""The password for robot %s has been successfully updated.""" % escape(robot_name)) + else: + messages.append("""The password for robot %s has been erased, and hence the robot %s does not exist anymore.""" % (escape(robot_name), escape(robot_name))) + action = '' + confirm = 0 + robot_name = '' + new_pwd1 = '' + new_pwd2 = '' + + else: + if not new_pwd1: + warnings.append("""By setting an empty password you will actually erase the robot %s""" % escape(robot_name)) + elif action == 'createurl': + if acce.acc_authorize_action(user_info, 'cfgrobotkeys', login_method=login_method, robot=robot_name)[0]: + errors.append("""You don't have proper authorization to create a URL for robot %s for login_method %s.""" % (escape(robot_name), escape(login_method))) + if login_method not in available_robot_login_methods: + errors.append("""The login method must be one among the available_robot_login_methods (%s).""" % escape(', '.join(available_robot_login_methods))) + if robot_name not in robot_keys.get(login_method, {}): + errors.append("""The robot name does not correspond to a valid robot name (for %s these are: %s).""" % (escape(login_method), escape(', '.join(robot_keys.get(login_method, {}).keys())))) + if json_assertion.strip(): + try: + assertion = json.loads(json_assertion) + assert(isinstance(assertion, dict)) + except Exception, err: + errors.append("""The assertion is not a valid json serializable mapping: %s""" % (err)) + else: + assertion = None + if not email: + errors.append("""The email is mandatory.""") + if not ip: + errors.append("""The IP address is mandatory.""") + if not errors: + url = CFG_EXTERNAL_AUTHENTICATION[login_method].test_create_example_url(email, login_method=login_method, robot=robot_name, ip=ip, timeout=time.time() + timeout, referer=referer, groups=groups.splitlines(), nickname=nickname, assertion=assertion) + if url_only: + req.content_type = 'text/plain' + return url + messages.append("""The corresponding URL is: %(url)s""" % { + 'url_escape': escape(url, True), + 'url': escape(url) + }) + action = '' + forms = """

Existing login_method:

    %s

""" % ''.join(["
  • %s (robots: %s)
  • " % (method, ', '.join(robot_keys.get(login_method, {}))) for method in available_robot_login_methods]) + forms += """

    Existing robot names (for login_method %s):

      %s

    """ % (escape(login_method), ''.join(["
  • %s
  • " % name for name in robot_keys.get(login_method, {})])) + confirm_field = """""" + if action == 'changepwd': + confirm_field = """ + Please confirm once more you want to change this password.""" + login_method_boxes = "" + for login_method_name in available_robot_login_methods: + if login_method_name == login_method: + login_method_boxes += """
    """ % {'name': escape(login_method_name, True)} + else: + login_method_boxes += """
    """ % {'name': escape(login_method_name, True)} + + forms += """ + """ % { + 'login_method': escape(login_method, True), + 'robot_name': escape(robot_name, True), + 'new_pwd1': escape(new_pwd1, True), + 'new_pwd2': escape(new_pwd2, True), + 'confirm_field': confirm_field, + 'login_method_boxes': login_method_boxes, + } + forms += """
    + + + + + + + +
    Login method:%(login_method_boxes)s
    %(confirm_field)s
    +
    + + + + + + + + + + + + + + +
    Login method:
    +
    """ % { + 'login_method_boxes': login_method_boxes, + 'robot_name': escape(robot_name, True), + 'timeout': escape(str(timeout), True), + 'referer': escape(referer, True), + 'ip': escape(ip, True), + 'email': escape(email, True), + 'nickname': escape(nickname, True), + 'groups': escape(groups), + 'json_assertion': escape(json_assertion) + } + out = "" + if errors: + out += "
    ERRORS
      " + for error in errors: + out += '
    • %s
    • ' % error + out += "
    " + if warnings: + out += "
    WARNINGS
      " + for warning in warnings: + out += '
    • %s
    • ' % warning + out += "
    " + if messages: + out += "
    INFORMATION
      " + for message in messages: + out += '
    • %s
    • ' % message + out += "
    " + + out += forms + return index(req=req, + title='Manage Robot Login', + subtitle='Are to manage robot-based authentiation', + body=out, + adminarea=2) + def perform_listgroups(req): """List all the existing groups.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) header = ['name'] groups = run_sql('select name from usergroup') output = tupletotable(header, groups) extra = """
    Create new role
    go here to add a new role.
    """ return index(req=req, title='Group list', subtitle='All the groups registered in the system', body=[output, extra], adminarea=2) def perform_rolearea(req, grep=""): """create the role area menu page.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) header = ['id', 'name', 'description', 'firewall like role definition', 'users', 'authorizations / actions', 'role', ''] roles = acca.acc_get_all_roles() roles2 = [] if grep: try: re_grep = re.compile(grep) except Exception, err: re_grep = None grep = '' else: re_grep = None for (id, name, desc, dummy, firerole_def_src) in roles: if not firerole_def_src: firerole_def_src = '' ## Workaround for None. if re_grep and not re_grep.search(name) and not re_grep.search(desc) and not re_grep.search(firerole_def_src): ## We're grepping for some word. ## Let's dig into the authorization then. all_actions = acca.acc_find_possible_actions_all(id) ## FIXME: the acc_find_possible_actions_all is really an ugly ## function, but is the closest to what it's needed in order ## to retrieve all the authorization of a role. for idx, row in enumerate(all_actions): grepped = False if idx % 2 == 0: ## even lines contains headers like in: ## ['role', 'action', '#', 'collection'] ## the only useful text to grep is from index 3 onwards for keyword in row[3:]: if re_grep.search(keyword): grepped = True break if grepped: break else: ## odd lines contains content like in: ## [1, 18L, 1, 'Theses'] ## the useful text to grep is indirectly index 1 ## which is indeed the id_action (needed to retrieve the ## action name) and from column 3 onwards. if re_grep.search(acca.acc_get_action_name(row[1])): break for value in row[3:]: if re_grep.search(value): grepped = True break if grepped: break else: ## We haven't grepped anything! ## Let's skip to the next role then... continue if len(desc) > 30: desc = desc[:30] + '...' if firerole_def_src and len(firerole_def_src) > 30: firerole_def_src = firerole_def_src[:30] + '...' roles2.append([id, name, desc, firerole_def_src]) for col in [(('add', 'adduserrole'), ('delete', 'deleteuserrole'),), (('add', 'addauthorization'), ('modify', 'modifyauthorizations'), ('remove', 'deleteroleaction')), (('modify', 'modifyrole'), ('delete', 'deleterole')), (('show details', 'showroledetails'), )]: roles2[-1].append('%s' % (col[0][1], id, col[0][0])) for (str, function) in col[1:]: roles2[-1][-1] += ' / %s' % \ (function, id, str) output = """
    Users:
    add or remove users from the access to a role and its priviliges.
    Authorizations/Actions:
    these terms means almost the same, but an authorization is a
    connection between a role and an action (possibly) containing arguments.
    Roles:
    see all the information attached to a role and decide if you want to
    delete it.
    Show only roles having any detail matching the regular expression:
    """ % escape(grep) output += tupletotable(header=header, tuple=roles2) extra = """
    Create new role
    go here to add a new role.
    """ return index(req=req, title='Role Administration', subtitle='administration with roles as access point', body=[output, extra], adminarea=2) def perform_actionarea(req, grep=''): """create the action area menu page.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) if grep: try: re_grep = re.compile(grep) except Exception, err: re_grep = None grep = '' else: re_grep = None header = ['name', 'authorizations/roles', ''] actions = acca.acc_get_all_actions() actions2 = [] roles2 = [] for (id, name, description) in actions: if re_grep and not re_grep.search(name) and not re_grep.search(description): grepped = False roles = acca.acc_get_action_roles(id) for id_role, role_name, role_description in roles: if re_grep.search(role_name) or re_grep.search(role_description): grepped = True break elif re_grep.search(acca.acc_get_role_details(id_role)[3] or ''): ## Found in FireRole grepped = True break else: details = acca.acc_find_possible_actions(id_role, id) if details: for argument in details[0][1:]: if re_grep.search(argument): grepped = True break for values in details[1:]: for value in values[1:]: if re_grep.search(value): grepped = True break if grepped: break if grepped: break if not grepped: continue actions2.append([name, description]) for col in [(('add', 'addauthorization'), ('modify', 'modifyauthorizations'), ('remove', 'deleteroleaction')), (('show details', 'showactiondetails'), )]: actions2[-1].append('%s' '' % (col[0][1], id, col[0][0])) for (str, function) in col[1:]: actions2[-1][-1] += ' / %s' % (function, id, str) output = """
    Authorizations/Roles:
    these terms means almost the same, but an authorization is a
    connection between a role and an action (possibly) containing arguments.
    Actions:
    see all the information attached to an action.
    Show only actions having any detail matching the regular expression:
    """ % escape(grep) output += tupletotable(header=header, tuple=actions2) extra = """
    Create new role
    go here to add a new role.
    """ return index(req=req, title='Action Administration', subtitle='administration with actions as access point', body=[output, extra], adminarea=2) def perform_userarea(req, email_user_pattern=''): """create area to show info about users. """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) subtitle = 'step 1 - search for users' output = """

    search for users to display.

    """ # remove letters not allowed in an email email_user_pattern = cleanstring_email(email_user_pattern) text = ' 1. search for user\n' text += ' \n' % (email_user_pattern, ) output += createhiddenform(action="userarea", text=text, button="search for users") if email_user_pattern: try: users1 = run_sql("""SELECT id, email FROM user WHERE email<>'' AND email RLIKE %s ORDER BY email LIMIT %s""", (email_user_pattern, MAXPAGEUSERS+1)) except OperationalError: users1 = () if not users1: output += '

    no matching users

    ' else: subtitle = 'step 2 - select what to do with user' users = [] for (id, email) in users1[:MAXPAGEUSERS]: users.append([id, email]) for col in [(('add', 'addroleuser'), ('remove', 'deleteuserrole')), (('show details', 'showuserdetails'), )]: users[-1].append('%s' % (col[0][1], email_user_pattern, id, col[0][0])) for (str, function) in col[1:]: users[-1][-1] += ' / %s' % \ (function, email_user_pattern, id, str) output += '

    found %s matching users:

    ' % \ (len(users1), ) output += tupletotable(header=['id', 'email', 'roles', ''], tuple=users) if len(users1) > MAXPAGEUSERS: output += '

    only showing the first %s users, ' \ 'narrow your search...

    ' % (MAXPAGEUSERS, ) return index(req=req, title='User Administration', subtitle=subtitle, body=[output], adminarea=2) def perform_resetarea(req): """create the reset area menu page.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) output = """
    Reset to Default Authorizations
    remove all changes that has been done to the roles and
    add only the default authorization settings.
    Add Default Authorizations
    keep all changes and add the default authorization settings.
    """ return index(req=req, title='Reset Authorizations', subtitle='reseting to or adding default authorizations', body=[output], adminarea=2) def perform_resetdefaultsettings(req, superusers=[], confirm=0): """delete all roles, actions and authorizations presently in the database and add only the default roles. only selected users will be added to superadmin, rest is blank """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) # cleaning input if type(superusers) == str: superusers = [superusers] # remove not valid e-mails for email in superusers: if not check_email(email): superusers.remove(email) # instructions output = """

    before you reset the settings, we need some users
    to connect to %s.
    enter as many e-mail addresses you want and press reset.
    confirm reset settings when you have added enough e-mails.
    %s is added as default.

    """ % (SUPERADMINROLE, CFG_SITE_ADMIN_EMAIL) # add more superusers output += """

    enter user e-mail addresses:

    """ for email in superusers: output += ' ' % (email, ) output += """ e-mail
    """ if superusers: # remove emails output += """
    have you entered wrong data?
    """ # superusers confirm table start = '
    ' extra = ' ' for email in superusers: extra += '' % (email, ) extra += ' ' end = '
    ' output += '

    reset default settings with the users below?

    ' output += tupletotable(header=['e-mail address'], tuple=superusers, start=start, extracolumn=extra, end=end) if confirm in [1, "1"]: res = acca.acc_reset_default_settings(superusers) if res: output += '

    successfully reset default settings

    ' else: output += '

    sorry, could not reset default settings

    ' return index(req=req, title='Reset Default Settings', subtitle='reset settings', body=[output], adminarea=6) def perform_adddefaultsettings(req, superusers=[], confirm=0): """add the default settings, and keep everything else. probably nothing will be deleted, except if there has been made changes to the defaults.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) # cleaning input if type(superusers) == str: superusers = [superusers] # remove not valid e-mails for email in superusers: if not check_email(email): superusers.remove(email) # instructions output = """

    before you add the settings, we need some users
    to connect to %s.
    enter as many e-mail addresses you want and press add.
    confirm add settings when you have added enough e-mails.
    %s is added as default.

    """ % (SUPERADMINROLE, CFG_SITE_ADMIN_EMAIL) # add more superusers output += """

    enter user e-mail addresses:

    """ for email in superusers: output += ' ' % (email, ) output += """ e-mail
    """ if superusers: # remove emails output += """
    have you entered wrong data?
    """ # superusers confirm table start = '
    ' extra = ' ' for email in superusers: extra += '' % (email, ) extra += ' ' end = '
    ' output += '

    add default settings with the users below?

    ' output += tupletotable(header=['e-mail address'], tuple=superusers, start=start, extracolumn=extra, end=end) if confirm in [1, "1"]: res = acca.acc_add_default_settings(superusers) if res: output += '

    successfully added default settings

    ' else: output += '

    sorry, could not add default settings

    ' return index(req=req, title='Add Default Settings', subtitle='add settings', body=[output], adminarea=6) def perform_manageaccounts(req, mtype='', content='', confirm=0): """start area for managing accounts.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) subtitle = 'Overview' fin_output = '' fin_output += """
    Menu
    0. Show all 1. Access policy 2. Account overview 3. Create account 4. Edit accounts
    - """ % (CFG_SITE_URL, CFG_SITE_URL, CFG_SITE_URL, CFG_SITE_URL, CFG_SITE_URL) + """ % (CFG_SITE_SECURE_URL, CFG_SITE_SECURE_URL, CFG_SITE_SECURE_URL, CFG_SITE_SECURE_URL, CFG_SITE_SECURE_URL) if mtype == "perform_accesspolicy" and content: fin_output += content elif mtype == "perform_accesspolicy" or mtype == "perform_showall": fin_output += perform_accesspolicy(req, callback='') fin_output += "
    " if mtype == "perform_accountoverview" and content: fin_output += content elif mtype == "perform_accountoverview" or mtype == "perform_showall": fin_output += perform_accountoverview(req, callback='') fin_output += "
    " if mtype == "perform_createaccount" and content: fin_output += content elif mtype == "perform_createaccount" or mtype == "perform_showall": fin_output += perform_createaccount(req, callback='') fin_output += "
    " if mtype == "perform_modifyaccounts" and content: fin_output += content elif mtype == "perform_modifyaccounts" or mtype == "perform_showall": fin_output += perform_modifyaccounts(req, callback='') fin_output += "
    " if mtype == "perform_becomeuser" and content: fin_output += content elif mtype == "perform_becomeuser" or mtype == "perform_showall": fin_output += perform_becomeuser(req, callback='') fin_output += "
    " return index(req=req, title='Manage Accounts', subtitle=subtitle, body=[fin_output], adminarea=0, authorized=1) def perform_accesspolicy(req, callback='yes', confirm=0): """Modify default behaviour of a guest user or if new accounts should automatically/manually be modified.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) - subtitle = """1. Access policy.   [?]""" % CFG_SITE_URL + subtitle = """1. Access policy.   [?]""" % CFG_SITE_SECURE_URL account_policy = {} account_policy[0] = "Users can register new accounts. New accounts automatically activated." account_policy[1] = "Users can register new accounts. Admin users must activate the accounts." account_policy[2] = "Only admin can register new accounts. User cannot edit email address." account_policy[3] = "Only admin can register new accounts. User cannot edit email address or password." account_policy[4] = "Only admin can register new accounts. User cannot edit email address,password or login method." site_policy = {} site_policy[0] = "Normal operation of the site." site_policy[1] = "Read-only site, all write operations temporarily closed." site_policy[2] = "Site fully closed." output = "(Modifications must be done in access_control_config.py)
    " output += "
    Current settings:
    " output += "Site status: %s
    " % (site_policy[CFG_ACCESS_CONTROL_LEVEL_SITE]) output += "Guest accounts allowed: %s
    " % (CFG_ACCESS_CONTROL_LEVEL_GUESTS == 0 and "Yes" or "No") output += "Account policy: %s
    " % (account_policy[CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS]) output += "Allowed email addresses limited: %s
    " % (CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN and CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN or "Not limited") output += "Send email to admin when new account: %s
    " % (CFG_ACCESS_CONTROL_NOTIFY_ADMIN_ABOUT_NEW_ACCOUNTS == 1 and "Yes" or "No") output += "Send email to user after creating new account: %s
    " % (CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT == 1 and "Yes" or "No") output += "Send email to user when account is activated: %s
    " % (CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_ACTIVATION == 1 and "Yes" or "No") output += "Send email to user when account is deleted/rejected: %s
    " % (CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_DELETION == 1 and "Yes" or "No") output += "
    " output += "Available 'login via' methods:
    " methods = CFG_EXTERNAL_AUTHENTICATION.keys() methods.sort() for system in methods: - output += """%s %s
    """ % (system, (CFG_EXTERNAL_AUTHENTICATION[system][1] and "(Default)" or "")) + output += """%s %s
    """ % (system, (CFG_EXTERNAL_AUTH_DEFAULT == system and "(Default)" or "")) output += "
    Changing the settings:
    " output += "Currently, all changes must be done using your favourite editor, and the webserver restarted for changes to take effect. For the settings to change, either look in the guide or in access_control_config.py ." body = [output] if callback: return perform_manageaccounts(req, "perform_accesspolicy", addadminbox(subtitle, body)) else: return addadminbox(subtitle, body) def perform_accountoverview(req, callback='yes', confirm=0): """Modify default behaviour of a guest user or if new accounts should automatically/manually be modified.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) - subtitle = """2. Account overview.   [?]""" % CFG_SITE_URL + subtitle = """2. Account overview.   [?]""" % CFG_SITE_SECURE_URL output = "" res = run_sql("SELECT COUNT(*) FROM user WHERE email=''") output += "Guest accounts: %s
    " % res[0][0] res = run_sql("SELECT COUNT(*) FROM user WHERE email!=''") output += "Registered accounts: %s
    " % res[0][0] res = run_sql("SELECT COUNT(*) FROM user WHERE email!='' AND note='0' OR note IS NULL") output += "Inactive accounts: %s " % res[0][0] if res[0][0] > 0: output += ' [Activate/Reject accounts]' res = run_sql("SELECT COUNT(*) FROM user") output += "
    Total nr of accounts: %s
    " % res[0][0] body = [output] if callback: return perform_manageaccounts(req, "perform_accountoverview", addadminbox(subtitle, body)) else: return addadminbox(subtitle, body) def perform_createaccount(req, email='', password='', callback='yes', confirm=0): """Modify default behaviour of a guest user or if new accounts should automatically/manually be modified.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) - subtitle = """3. Create account.   [?]""" % CFG_SITE_URL + subtitle = """3. Create account.   [?]""" % CFG_SITE_SECURE_URL output = "" text = ' Email:\n' text += '
    ' % (email, ) text += ' Password:\n' text += '
    ' % (password, ) output += createhiddenform(action="createaccount", text=text, confirm=1, button="Create") if confirm in [1, "1"] and email and email_valid_p(email): res = run_sql("SELECT email FROM user WHERE email=%s", (email,)) if not res: res = run_sql("INSERT INTO user (email,password, note) values(%s,AES_ENCRYPT(email,%s), '1')", (email, password)) if CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT == 1: emailsent = send_new_user_account_warning(email, email, password) == 0 if password: output += 'Account created with password and activated.' else: output += 'Account created without password and activated.' if CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT == 1: if emailsent: output += '
    An email has been sent to the owner of the account.' else: output += '
    Could not send an email to the owner of the account.' else: output += 'An account with the same email already exists.' elif confirm in [1, "1"]: output += 'Please specify an valid email-address.' body = [output] if callback: return perform_manageaccounts(req, "perform_createaccount", addadminbox(subtitle, body)) else: return addadminbox(subtitle, body) def perform_modifyaccountstatus(req, userID, email_user_pattern, limit_to, maxpage, page, callback='yes', confirm=0): """set a disabled account to enabled and opposite""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) res = run_sql("SELECT id, email, note FROM user WHERE id=%s", (userID, )) subtitle = "" output = "" if res: if res[0][2] in [0, "0", None]: res2 = run_sql("UPDATE user SET note=1 WHERE id=%s", (userID, )) output += """The account '%s' has been activated.""" % res[0][1] if CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_ACTIVATION == 1: emailsent = send_account_activated_message(res[0][1], res[0][1], '*****') if emailsent: output += """
    An email has been sent to the owner of the account.""" else: output += """
    Could not send an email to the owner of the account.""" elif res[0][2] in [1, "1"]: res2 = run_sql("UPDATE user SET note=0 WHERE id=%s", (userID, )) output += """The account '%s' has been set inactive.""" % res[0][1] else: output += 'The account id given does not exist.' body = [output] if callback: return perform_modifyaccounts(req, email_user_pattern, limit_to, maxpage, page, content=output, callback='yes') else: return addadminbox(subtitle, body) def perform_editaccount(req, userID, mtype='', content='', callback='yes', confirm=-1): """form to modify an account. this method is calling other methods which again is calling this and sending back the output of the method. if callback, the method will call perform_editcollection, if not, it will just return its output. userID - id of the user mtype - the method that called this method. content - the output from that method.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) res = run_sql("SELECT id, email FROM user WHERE id=%s", (userID, )) if not res: if mtype == "perform_deleteaccount": text = """The selected account has been deleted, to continue editing, go back to 'Manage Accounts'.""" if CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_DELETION == 1: text += """
    An email has been sent to the owner of the account.""" else: text = """The selected accounts does not exist, please go back and select an account to edit.""" return index(req=req, title='Edit Account', subtitle="Edit account", body=[text], adminarea=7, authorized=1) fin_output = """
    Menu
    0. Show all 1. Modify login-data 2. Modify preferences
    3. Delete account
    - """ % (CFG_SITE_URL, userID, CFG_SITE_URL, userID, CFG_SITE_URL, userID, CFG_SITE_URL, userID) + """ % (CFG_SITE_SECURE_URL, userID, CFG_SITE_SECURE_URL, userID, CFG_SITE_SECURE_URL, userID, CFG_SITE_SECURE_URL, userID) if mtype == "perform_modifylogindata" and content: fin_output += content elif mtype == "perform_modifylogindata" or not mtype: fin_output += perform_modifylogindata(req, userID, callback='') if mtype == "perform_modifypreferences" and content: fin_output += content elif mtype == "perform_modifypreferences" or not mtype: fin_output += perform_modifypreferences(req, userID, callback='') if mtype == "perform_deleteaccount" and content: fin_output += content elif mtype == "perform_deleteaccount" or not mtype: fin_output += perform_deleteaccount(req, userID, callback='') return index(req=req, title='Edit Account', subtitle="Edit account '%s'" % res[0][1], body=[fin_output], adminarea=7, authorized=1) def perform_becomeuser(req, userID='', callback='yes', confirm=0): """modify email and password of an account""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) - subtitle = """5. Became user.   [?]""" % CFG_SITE_URL + subtitle = """5. Became user.   [?]""" % CFG_SITE_SECURE_URL res = run_sql("SELECT email FROM user WHERE id=%s", (userID, )) output = "" if res: update_Uid(req, res[0][0]) - redirect_to_url(req, CFG_SITE_URL) + redirect_to_url(req, CFG_SITE_SECURE_URL) else: output += 'The account id given does not exist.' body = [output] if callback: return perform_editaccount(req, userID, mtype='perform_becomeuser', content=addadminbox(subtitle, body), callback='yes') else: return addadminbox(subtitle, body) def perform_modifylogindata(req, userID, nickname='', email='', password='', callback='yes', confirm=0): """modify email and password of an account""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) - subtitle = """1. Edit login-data.   [?]""" % CFG_SITE_URL + subtitle = """1. Edit login-data.   [?]""" % CFG_SITE_SECURE_URL res = run_sql("SELECT id, email, nickname FROM user WHERE id=%s", (userID, )) output = "" if res: if not email and not password: email = res[0][1] nickname = res[0][2] text = ' Account id:%s
    \n' % userID text = ' Nickname:\n' text += '
    ' % (nickname, ) text += ' Email:\n' text += '
    ' % (email, ) text += ' Password:\n' text += '
    ' % (password, ) output += createhiddenform(action="modifylogindata", text=text, userID=userID, confirm=1, button="Modify") if confirm in [1, "1"] and email and email_valid_p(email): res = run_sql("SELECT nickname FROM user WHERE nickname=%s AND id<>%s", (nickname, userID)) if res: output += 'Sorry, the specified nickname is already used.' else: res = run_sql("UPDATE user SET email=%s WHERE id=%s", (email, userID)) if password: res = run_sql("UPDATE user SET password=AES_ENCRYPT(email,%s) WHERE id=%s", (password, userID)) else: output += 'Password not modified. ' res = run_sql("UPDATE user SET nickname=%s WHERE id=%s", (nickname, userID)) output += 'Nickname/email and/or password modified.' elif confirm in [1, "1"]: output += 'Please specify an valid email-address.' else: output += 'The account id given does not exist.' body = [output] if callback: return perform_editaccount(req, userID, mtype='perform_modifylogindata', content=addadminbox(subtitle, body), callback='yes') else: return addadminbox(subtitle, body) def perform_modifypreferences(req, userID, login_method='', callback='yes', confirm=0): """modify email and password of an account""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) - subtitle = """2. Modify preferences.   [?]""" % CFG_SITE_URL + subtitle = """2. Modify preferences.   [?]""" % CFG_SITE_SECURE_URL res = run_sql("SELECT id, email FROM user WHERE id=%s", (userID, )) output = "" if res: user_pref = get_user_preferences(userID) if confirm in [1, "1"]: if login_method: user_pref['login_method'] = login_method set_user_preferences(userID, user_pref) output += "Select default login method:
    " text = "" methods = CFG_EXTERNAL_AUTHENTICATION.keys() methods.sort() for system in methods: text += """%s
    """ % (system, (user_pref['login_method'] == system and "checked" or ""), system) output += createhiddenform(action="modifypreferences", text=text, confirm=1, userID=userID, button="Select") if confirm in [1, "1"]: if login_method: output += """The login method has been changed""" else: output += """Nothing to update""" else: output += 'The account id given does not exist.' body = [output] if callback: return perform_editaccount(req, userID, mtype='perform_modifypreferences', content=addadminbox(subtitle, body), callback='yes') else: return addadminbox(subtitle, body) def perform_deleteaccount(req, userID, callback='yes', confirm=0): """delete account""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) - subtitle = """3. Delete account.   [?]""" % CFG_SITE_URL + subtitle = """3. Delete account.   [?]""" % CFG_SITE_SECURE_URL res = run_sql("SELECT id, email FROM user WHERE id=%s", (userID, )) output = "" if res: if confirm in [0, "0"]: text = 'Are you sure you want to delete the account with email: "%s"?' % res[0][1] output += createhiddenform(action="deleteaccount", text=text, userID=userID, confirm=1, button="Delete") elif confirm in [1, "1"]: res2 = run_sql("DELETE FROM user WHERE id=%s", (userID, )) output += 'Account deleted.' if CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_DELETION == 1: emailsent = send_account_deleted_message(res[0][1], res[0][1]) else: output += 'The account id given does not exist.' body = [output] if callback: return perform_editaccount(req, userID, mtype='perform_deleteaccount', content=addadminbox(subtitle, body), callback='yes') else: return addadminbox(subtitle, body) def perform_rejectaccount(req, userID, email_user_pattern, limit_to, maxpage, page, callback='yes', confirm=0): """Delete account and send an email to the owner.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) res = run_sql("SELECT id, email, note FROM user WHERE id=%s", (userID, )) output = "" subtitle = "" if res: res2 = run_sql("DELETE FROM user WHERE id=%s", (userID, )) output += 'Account rejected and deleted.' if CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_DELETION == 1: if not res[0][2] or res[0][2] == "0": emailsent = send_account_rejected_message(res[0][1], res[0][1]) elif res[0][2] == "1": emailsent = send_account_deleted_message(res[0][1], res[0][1]) if emailsent: output += """
    An email has been sent to the owner of the account.""" else: output += """
    Could not send an email to the owner of the account.""" else: output += 'The account id given does not exist.' body = [output] if callback: return perform_modifyaccounts(req, email_user_pattern, limit_to, maxpage, page, content=output, callback='yes') else: return addadminbox(subtitle, body) def perform_modifyaccounts(req, email_user_pattern='', limit_to=-1, maxpage=MAXPAGEUSERS, page=1, content='', callback='yes', confirm=0): """Modify default behaviour of a guest user or if new accounts should automatically/manually be modified.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) - subtitle = """4. Edit accounts.   [?]""" % CFG_SITE_URL + subtitle = """4. Edit accounts.   [?]""" % CFG_SITE_SECURE_URL output = "" # remove letters not allowed in an email email_user_pattern = cleanstring_email(email_user_pattern) try: maxpage = int(maxpage) except: maxpage = MAXPAGEUSERS try: page = int(page) if page < 1: page = 1 except: page = 1 text = ' Email (part of):\n' text += '
    ' % (email_user_pattern, ) text += """Limit to:
    """ % ((limit_to=="all" and "selected" or ""), (limit_to=="enabled" and "selected" or ""), (limit_to=="disabled" and "selected" or "")) text += """Accounts per page:
    """ % ((maxpage==25 and "selected" or ""), (maxpage==50 and "selected" or ""), (maxpage==100 and "selected" or ""), (maxpage==250 and "selected" or ""), (maxpage==500 and "selected" or ""), (maxpage==1000 and "selected" or "")) output += createhiddenform(action="modifyaccounts", text=text, button="search for accounts") if limit_to not in [-1, "-1"] and maxpage: options = [] users1 = "SELECT id,email,note FROM user WHERE " if limit_to == "enabled": users1 += " email!='' AND note=1" elif limit_to == "disabled": users1 += " email!='' AND note=0 OR note IS NULL" elif limit_to == "guest": users1 += " email=''" else: users1 += " email!=''" if email_user_pattern: users1 += " AND email RLIKE %s" options += [email_user_pattern] users1 += " ORDER BY email LIMIT %s" options += [maxpage * page + 1] try: users1 = run_sql(users1, tuple(options)) except OperationalError: users1 = () if not users1: output += 'There are no accounts matching the email given.' else: users = [] if maxpage * (page - 1) > len(users1): page = len(users1) / maxpage + 1 for (id, email, note) in users1[maxpage * (page - 1):(maxpage * page)]: users.append(['', id, email, (note=="1" and 'Active' or 'Inactive')]) for col in [(((note=="1" and 'Inactivate' or 'Activate'), 'modifyaccountstatus'), ((note == "0" and 'Reject' or 'Delete'), 'rejectaccount'), ), (('Edit account', 'editaccount'), ),]: users[-1].append('%s' % (col[0][1], id, email_user_pattern, limit_to, maxpage, page, random.randint(0, 1000), col[0][0])) for (str, function) in col[1:]: users[-1][-1] += ' / %s' % (function, id, email_user_pattern, limit_to, maxpage, page, random.randint(0, 1000), str) users[-1].append('%s' % ('becomeuser', id, email_user_pattern, limit_to, maxpage, page, random.randint(0, 1000), 'Become user')) last = "" next = "" if len(users1) > maxpage: if page > 1: last += 'Last Page' % (email_user_pattern, limit_to, maxpage, (page - 1)) if len(users1[maxpage * (page - 1):(maxpage * page)]) == maxpage: next += 'Next page' % (email_user_pattern, limit_to, maxpage, (page + 1)) output += 'Showing accounts %s-%s:' % (1 + maxpage * (page - 1), maxpage * page) else: output += '%s matching account(s):' % len(users1) output += tupletotable(header=[last, 'id', 'email', 'Status', '', '', next], tuple=users) else: output += 'Please select which accounts to find and how many to show per page.' if content: output += "
    %s" % content body = [output] if callback: return perform_manageaccounts(req, "perform_modifyaccounts", addadminbox(subtitle, body)) else: return addadminbox(subtitle, body) def perform_delegate_startarea(req): """start area for lower level delegation of rights.""" # refuse access to guest users: uid = getUid(req) if isGuestUser(uid): return index(req=req, title='Delegate Rights', adminarea=0, authorized=0) subtitle = 'select what to do' output = '' if is_adminuser(req)[0] == 0: output += """

    You are also allowed to be in the Main Admin Area which gives you
    the access to the full functionality of WebAccess.

    """ output += """
    Connect users to roles
    add users to the roles you have delegation rights to.
    Remove users from roles
    remove users from the roles you have delegation rights to.
    Set up delegation rights
    specialized area to set up the delegation rights used in the areas above.
    you need to be a web administrator to access the area.
    """ return index(req=req, title='Delegate Rights', subtitle=subtitle, body=[output], adminarea=0, authorized=1) def perform_delegate_adminsetup(req, id_role_admin=0, id_role_delegate=0, confirm=0): """lets the webadmins set up the delegation rights for the other roles id_role_admin - the role to be given delegation rights id_role_delegate - the role over which the delegation rights are given confirm - make the connection happen """ subtitle = 'step 1 - select admin role' admin_roles = acca.acc_get_all_roles() output = """

    This is a specialized area to handle a task that also can be handled
    from the "add authorization" interface.

    By handling the delegation rights here you get the advantage of
    not having to select the correct action (%s) or
    remembering the names of available roles.

    """ % (DELEGATEADDUSERROLE, ) output += createroleselect(id_role=id_role_admin, step=1, button='select admin role', name='id_role_admin', action='delegate_adminsetup', roles=admin_roles) if str(id_role_admin) != '0': subtitle = 'step 2 - select delegate role' name_role_admin = acca.acc_get_role_name(id_role=id_role_admin) delegate_roles_old = acca.acc_find_delegated_roles(id_role_admin=id_role_admin) delegate_roles = [] delegate_roles_old_names = [] for role in admin_roles: if (role,) not in delegate_roles_old: delegate_roles.append(role) else: delegate_roles_old_names.append(role[1]) if delegate_roles_old_names: delegate_roles_old_names.sort() names_str = '' for name in delegate_roles_old_names: if names_str: names_str += ', ' names_str += name output += '

    previously selected roles: %s.

    ' % (names_str, ) extra = """
    Remove delegated roles
    use the standard administration area to remove delegation rights you no longer want to be available.
    """ % (id_role_admin, acca.acc_get_action_id(name_action=DELEGATEADDUSERROLE)) else: output += '

    no previously selected roles.

    ' output += createroleselect(id_role=id_role_delegate, step=2, button='select delegate role', name='id_role_delegate', action='delegate_adminsetup', roles=delegate_roles, id_role_admin=id_role_admin) if str(id_role_delegate) != '0': subtitle = 'step 3 - confirm to add delegation right' name_role_delegate = acca.acc_get_role_name(id_role=id_role_delegate) output += """

    Warning: don't hand out delegation rights that can harm the system (e.g. delegating superrole).

    """ output += createhiddenform(action="delegate_adminsetup", text='let role %s delegate rights over role %s?' % (name_role_admin, name_role_delegate), id_role_admin=id_role_admin, id_role_delegate=id_role_delegate, confirm=1) if int(confirm): subtitle = 'step 4 - confirm delegation right added' # res1 = acca.acc_add_role_action_arguments_names(name_role=name_role_admin, # name_action=DELEGATEADDUSERROLE, # arglistid=-1, # optional=0, # role=name_role_delegate) res1 = acca.acc_add_authorization(name_role=name_role_admin, name_action=DELEGATEADDUSERROLE, optional=0, role=name_role_delegate) if res1: output += '

    confirm: role %s delegates role %s.' % (name_role_admin, name_role_delegate) else: output += '

    sorry, delegation right could not be added,
    it probably already exists.

    ' # see if right hand menu is available try: body = [output, extra] except NameError: body = [output] return index(req=req, title='Delegate Rights', subtitle=subtitle, body=body, adminarea=1) def perform_delegate_adduserrole(req, id_role=0, email_user_pattern='', id_user=0, confirm=0): """let a lower level web admin add users to a limited set of roles. id_role - the role to connect to a user id_user - the user to connect to a role confirm - make the connection happen """ # finding the allowed roles for this user id_admin = getUid(req) id_action = acca.acc_get_action_id(name_action=DELEGATEADDUSERROLE) actions = acca.acc_find_possible_actions_user(id_user=id_admin, id_action=id_action) allowed_roles = [] allowed_id_roles = [] for (id, arglistid, name_role_help) in actions[1:]: id_role_help = acca.acc_get_role_id(name_role=name_role_help) if id_role_help and [id_role_help, name_role_help, ''] not in allowed_roles: allowed_roles.append([id_role_help, name_role_help, '']) allowed_id_roles.append(str(id_role_help)) output = '' if not allowed_roles: subtitle = 'no delegation rights' output += """

    You do not have the delegation rights over any roles.
    If you think you should have such rights, contact a WebAccess Administrator.

    """ extra = '' else: subtitle = 'step 1 - select role' output += """

    Lower level delegation of access rights to roles.
    An administrator with all rights have to give you these rights.

    """ email_out = acca.acc_get_user_email(id_user=id_user) name_role = acca.acc_get_role_name(id_role=id_role) output += createroleselect(id_role=id_role, step=1, name='id_role', action='delegate_adduserrole', roles=allowed_roles) if str(id_role) != '0' and str(id_role) in allowed_id_roles: subtitle = 'step 2 - search for users' # remove letters not allowed in an email email_user_pattern = cleanstring_email(email_user_pattern) text = ' 2. search for user \n' text += ' \n' % (email_user_pattern, ) output += createhiddenform(action="delegate_adduserrole", text=text, button="search for users", id_role=id_role) # pattern is entered if email_user_pattern: # users with matching email-address try: users1 = run_sql("""SELECT id, email FROM user WHERE email<>'' AND email RLIKE %s ORDER BY email """, (email_user_pattern, )) except OperationalError: users1 = () # users that are connected try: users2 = run_sql("""SELECT DISTINCT u.id, u.email FROM user u LEFT JOIN user_accROLE ur ON u.id = ur.id_user WHERE ur.id_accROLE = %s AND u.email RLIKE %s ORDER BY u.email """, (id_role, email_user_pattern)) except OperationalError: users2 = () # no users that match the pattern if not (users1 or users2): output += '

    no qualified users, try new search.

    ' # too many matching users elif len(users1) > MAXSELECTUSERS: output += '

    %s hits, too many qualified users, specify more narrow search. (limit %s)

    ' % (len(users1), MAXSELECTUSERS) # show matching users else: subtitle = 'step 3 - select a user' users = [] extrausers = [] for (id, email) in users1: if (id, email) not in users2: users.append([id,email,'']) for (id, email) in users2: extrausers.append([-id, email,'']) output += createuserselect(id_user=id_user, action="delegate_adduserrole", step=3, users=users, extrausers=extrausers, button="add this user", id_role=id_role, email_user_pattern=email_user_pattern) try: id_user = int(id_user) except ValueError: pass # user selected already connected to role if id_user < 0: output += '

    users in brackets are already attached to the role, try another one...

    ' # a user is selected elif email_out: subtitle = "step 4 - confirm to add user" output += createhiddenform(action="delegate_adduserrole", text='add user %s to role %s?' % (email_out, name_role), id_role=id_role, email_user_pattern=email_user_pattern, id_user=id_user, confirm=1) # it is confirmed that this user should be added if confirm: # add user result = acca.acc_add_user_role(id_user=id_user, id_role=id_role) if result and result[2]: subtitle = 'step 5 - confirm user added' output += '

    confirm: user %s added to role %s.

    ' % (email_out, name_role) else: subtitle = 'step 5 - user could not be added' output += '

    sorry, but user could not be added.

    ' extra = """
    Remove users from role
    remove users from the roles you have delegating rights to.
    """ % (id_role, ) return index(req=req, title='Connect users to roles', subtitle=subtitle, body=[output, extra], adminarea=1, authorized=1) def perform_delegate_deleteuserrole(req, id_role=0, id_user=0, confirm=0): """let a lower level web admin remove users from a limited set of roles. id_role - the role to connect to a user id_user - the user to connect to a role confirm - make the connection happen """ subtitle = 'in progress...' output = '

    in progress...

    ' # finding the allowed roles for this user id_admin = getUid(req) id_action = acca.acc_get_action_id(name_action=DELEGATEADDUSERROLE) actions = acca.acc_find_possible_actions_user(id_user=id_admin, id_action=id_action) output = '' if not actions: subtitle = 'no delegation rights' output += """

    You do not have the delegation rights over any roles.
    If you think you should have such rights, contact a WebAccess Administrator.

    """ extra = '' else: subtitle = 'step 1 - select role' output += """

    Lower level delegation of access rights to roles.
    An administrator with all rights have to give you these rights.

    """ email_out = acca.acc_get_user_email(id_user=id_user) name_role = acca.acc_get_role_name(id_role=id_role) # create list of allowed roles allowed_roles = [] allowed_id_roles = [] for (id, arglistid, name_role_help) in actions[1:]: id_role_help = acca.acc_get_role_id(name_role=name_role_help) if id_role_help and [id_role_help, name_role_help, ''] not in allowed_roles: allowed_roles.append([id_role_help, name_role_help, '']) allowed_id_roles.append(str(id_role_help)) output += createroleselect(id_role=id_role, step=1, action='delegate_deleteuserrole', roles=allowed_roles) if str(id_role) != '0' and str(id_role) in allowed_id_roles: subtitle = 'step 2 - select user' users = acca.acc_get_role_users(id_role) output += createuserselect(id_user=id_user, step=2, action='delegate_deleteuserrole', users=users, id_role=id_role) if str(id_user) != '0': subtitle = 'step 3 - confirm delete of user' email_user = acca.acc_get_user_email(id_user=id_user) output += createhiddenform(action="delegate_deleteuserrole", text='delete user %s from %s?' % (headerstrong(user=id_user), headerstrong(role=id_role)), id_role=id_role, id_user=id_user, confirm=1) if confirm: res = acca.acc_delete_user_role(id_user=id_user, id_role=id_role) if res: subtitle = 'step 4 - confirm user deleted from role' output += '

    confirm: deleted user %s from role %s.

    ' % (email_user, name_role) else: subtitle = 'step 4 - user could not be deleted' output += 'sorry, but user could not be deleted
    user is probably already deleted.' extra = """
    Connect users to role
    add users to the roles you have delegating rights to.
    """ % (id_role, ) return index(req=req, title='Remove users from roles', subtitle=subtitle, body=[output, extra], adminarea=1, authorized=1) def perform_showactiondetails(req, id_action): """show the details of an action. """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) output = createactionselect(id_action=id_action, action="showactiondetails", step=1, actions=acca.acc_get_all_actions(), button="select action") if id_action not in [0, '0']: output += actiondetails(id_action=id_action) extra = """
    Add new authorization
    add an authorization.
    Modify authorizations
    modify existing authorizations.
    Remove role
    remove all authorizations from action and a role.
    """ % (id_action, id_action, id_action) body = [output, extra] else: output += '

    no details to show

    ' body = [output] return index(req=req, title='Show Action Details', subtitle='show action details', body=body, adminarea=4) def actiondetails(id_action=0): """show details of given action. """ output = '' if id_action not in [0, '0']: name_action = acca.acc_get_action_name(id_action=id_action) output += '

    action details:

    ' output += tupletotable(header=['id', 'name', 'description', 'allowedkeywords', 'optional'], tuple=[acca.acc_get_action_details(id_action=id_action)]) roleshlp = acca.acc_get_action_roles(id_action=id_action) if roleshlp: roles = [] for (id, name, dummy) in roleshlp: res = acca.acc_find_possible_actions(id, id_action) if res: authorization_details = tupletotable(header=res[0], tuple=res[1:]) else: authorization_details = 'no details to show' roles.append([id, name, authorization_details, 'show connected users' % (id, )]) roletable = tupletotable(header=['id', 'name', 'authorization details', ''], tuple=roles) output += '

    roles connected to %s:

    \n' % (headerstrong(action=name_action, query=0), ) output += roletable else: output += '

    no roles connected to %s.

    \n' % (headerstrong(action=name_action, query=0), ) else: output += '

    no details to show

    ' return output def perform_addrole(req, id_role=0, name_role='', description='put description here.', firerole_def_src=CFG_ACC_EMPTY_ROLE_DEFINITION_SRC, confirm=0): """form to add a new role with these values: name_role - name of the new role description - optional description of the role """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) name_role = cleanstring(name_role) title='Add Role' subtitle = 'step 1 - give values to the requested fields' output = """
    role name
    description
    firewall like role definition [?]
    See the list of groups for a hint about which group names you can use.
    """ % (escape(name_role, '"'), escape(description), escape(firerole_def_src)) if name_role: # description must be changed before submitting subtitle = 'step 2 - confirm to add role' internaldesc = '' if description != 'put description here.': internaldesc = description try: firerole_def_ser = serialize(compile_role_definition(firerole_def_src)) except InvenioWebAccessFireroleError, msg: output += "%s" % msg else: text = """ add role with:
    \n name: %s
    """ % (name_role, ) if internaldesc: text += 'description: %s?\n' % (description, ) output += createhiddenform(action="addrole", text=text, name_role=escape(name_role, '"'), description=escape(description, '"'), firerole_def_src=escape(firerole_def_src, '"'), confirm=1) if confirm not in ["0", 0]: result = acca.acc_add_role(name_role=name_role, description=internaldesc, firerole_def_ser=firerole_def_ser, firerole_def_src=firerole_def_src) if result: subtitle = 'step 3 - role added' output += '

    role added:

    ' result = list(result) result[3] = result[3].replace('\n', '
    ') result = tuple(result) output += tupletotable(header=['id', 'role name', 'description', 'firewall like role definition'], tuple=[result]) else: subtitle = 'step 3 - role could not be added' output += '

    sorry, could not add role,
    role with the same name probably exists.

    ' id_role = acca.acc_get_role_id(name_role=name_role) extra = """
    Add authorization
    start adding new authorizations to role %s.
    Connect user
    connect a user to role %s.
    """ % (id_role, name_role, id_role, name_role) try: body = [output, extra] except NameError: body = [output] return index(req=req, title=title, body=body, subtitle=subtitle, adminarea=3) def perform_modifyrole(req, id_role='0', name_role='', description='put description here.', firerole_def_src='', modified='0', confirm=0): """form to add a new role with these values: name_role - name of the role to be changed description - optional description of the role firerole_def_src - optional firerole like definition of the role """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) ret = acca.acc_get_role_details(id_role) if ret and modified =='0': name_role = ret[1] description = ret[2] firerole_def_src = ret[3] if not firerole_def_src or firerole_def_src == '' or firerole_def_src is None: firerole_def_src = 'deny any' name_role = cleanstring(name_role) title='Modify Role' subtitle = 'step 1 - give values to the requested fields and confirm to modify role' output = """
    role name
    description
    firewall like role definition [?]
    See the list of groups for a hint about which group names you can use.
    """ % (id_role, escape(name_role), escape(description), escape(firerole_def_src)) if modified in [1, '1']: # description must be changed before submitting internaldesc = '' if description != 'put description here.': internaldesc = description text = """ modify role with:
    \n name: %s
    """ % (name_role, ) if internaldesc: text += 'description: %s?
    ' % (description, ) text += 'firewall like role definition: %s' % firerole_def_src.replace('\n', '
    ') try: firerole_def_ser = serialize(compile_role_definition(firerole_def_src)) except InvenioWebAccessFireroleError, msg: subtitle = 'step 2 - role could not be modified' output += '

    sorry, could not modify role because of troubles with its definition:
    %s

    ' % msg else: output += createhiddenform(action="modifyrole", text=text, id_role = id_role, name_role=escape(name_role, True), description=escape(description, True), firerole_def_src=escape(firerole_def_src, True), modified=1, confirm=1) if confirm not in ["0", 0]: result = acca.acc_update_role(id_role, name_role=name_role, description=internaldesc, firerole_def_ser=firerole_def_ser, firerole_def_src=firerole_def_src) if result: subtitle = 'step 2 - role modified' output += '

    role modified:

    ' output += tupletotable(header=['id', 'role name', 'description', 'firewall like role definition'], tuple=[(id_role, name_role, description, firerole_def_src.replace('\n', '
    '))]) else: subtitle = 'step 2 - role could not be modified' output += '

    sorry, could not modify role,
    please contact the administrator.

    ' body = [output] return index(req=req, title=title, body=body, subtitle=subtitle, adminarea=3) def perform_deleterole(req, id_role="0", confirm=0): """select a role and show all connected information, users - users that can access the role. actions - actions with possible authorizations.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) title = 'Delete role' subtitle = 'step 1 - select role to delete' name_role = acca.acc_get_role_name(id_role=id_role) output = createroleselect(id_role=id_role, action="deleterole", step=1, roles=acca.acc_get_all_roles(), button="delete role") if id_role != "0" and name_role: subtitle = 'step 2 - confirm delete of role' output += roledetails(id_role=id_role) output += createhiddenform(action="deleterole", text='delete role %s and all connections?' % (name_role, ), id_role=id_role, confirm=1) if confirm: res = acca.acc_delete_role(id_role=id_role) subtitle = 'step 3 - confirm role deleted' if res: output += "

    confirm: role %s deleted.
    " % (name_role, ) output += "%s entries were removed.

    " % (res, ) else: output += "

    sorry, the role could not be deleted.

    " elif id_role != "0": output += '

    the role has been deleted...

    ' return index(req=req, title=title, subtitle=subtitle, body=[output], adminarea=3) def perform_showroledetails(req, id_role): """show the details of a role.""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) output = createroleselect(id_role=id_role, action="showroledetails", step=1, roles=acca.acc_get_all_roles(), button="select role") if id_role not in [0, '0']: name_role = acca.acc_get_role_name(id_role=id_role) output += roledetails(id_role=id_role) extra = """
    Modify role
    modify the role you are seeing
    Add new authorization
    add an authorization.
    Modify authorizations
    modify existing authorizations.
    Connect user
    connect a user to role %(name_role)s.
    Remove user
    remove a user from role %(name_role)s.
    """ % {'id_role' : id_role, 'name_role' : name_role} body = [output, extra] else: output += '

    no details to show

    ' body = [output] return index(req=req, title='Show Role Details', subtitle='show role details', body=body, adminarea=3) def roledetails(id_role=0): """create the string to show details about a role. """ name_role = acca.acc_get_role_name(id_role=id_role) usershlp = acca.acc_get_role_users(id_role) users = [] for (id, email, dummy) in usershlp: users.append([id, email, 'show user details' % (id, )]) usertable = tupletotable(header=['id', 'email'], tuple=users) actionshlp = acca.acc_get_role_actions(id_role) actions = [] for (action_id, name, dummy) in actionshlp: res = acca.acc_find_possible_actions(id_role, action_id) if res: authorization_details = tupletotable(header=res[0], tuple=res[1:]) else: authorization_details = 'no details to show' actions.append([action_id, name, authorization_details, 'show action details' % (id_role, action_id)]) actiontable = tupletotable(header=['id', 'name', 'parameters', ''], tuple=actions) # show role details details = '

    role details:

    ' role_details = acca.acc_get_role_details(id_role=id_role) if role_details[3] is None: role_details[3] = '' role_details[3] = role_details[3].replace('\n', '
    ') # Hack for preformatting firerole rules details += tupletotable(header=['id', 'name', 'description', 'firewall like role definition'], tuple=[role_details]) # show connected users details += '

    users connected to %s:

    ' % (headerstrong(role=name_role, query=0), ) if users: details += usertable else: details += '

    no users connected.

    ' # show connected authorizations details += '

    authorizations for %s:

    ' % (headerstrong(role=name_role, query=0), ) if actions: details += actiontable else: details += '

    no authorizations connected

    ' return details def perform_adduserrole(req, id_role='0', email_user_pattern='', id_user='0', confirm=0): """create connection between user and role. id_role - id of the role to add user to email_user_pattern - search for users using this pattern id_user - id of user to add to the role. """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) email_out = acca.acc_get_user_email(id_user=id_user) name_role = acca.acc_get_role_name(id_role=id_role) title = 'Connect user to role ' subtitle = 'step 1 - select a role' output = createroleselect(id_role=id_role, action="adduserrole", step=1, roles=acca.acc_get_all_roles()) # role is selected if id_role != "0": title += name_role subtitle = 'step 2 - search for users' # remove letters not allowed in an email email_user_pattern = cleanstring_email(email_user_pattern) text = ' 2. search for user \n' text += ' \n' % (email_user_pattern, ) output += createhiddenform(action="adduserrole", text=text, button="search for users", id_role=id_role) # pattern is entered if email_user_pattern: # users with matching email-address try: users1 = run_sql("""SELECT id, email FROM user WHERE email<>'' AND email RLIKE %s ORDER BY email """, (email_user_pattern, )) except OperationalError: users1 = () # users that are connected try: users2 = run_sql("""SELECT DISTINCT u.id, u.email FROM user u LEFT JOIN user_accROLE ur ON u.id = ur.id_user WHERE ur.id_accROLE = %s AND u.email RLIKE %s ORDER BY u.email """, (id_role, email_user_pattern)) except OperationalError: users2 = () # no users that match the pattern if not (users1 or users2): output += '

    no qualified users, try new search.

    ' elif len(users1) > MAXSELECTUSERS: output += '

    %s hits, too many qualified users, specify more narrow search. (limit %s)

    ' % (len(users1), MAXSELECTUSERS) # show matching users else: subtitle = 'step 3 - select a user' users = [] extrausers = [] for (user_id, email) in users1: if (user_id, email) not in users2: users.append([user_id,email,'']) for (user_id, email) in users2: extrausers.append([-user_id, email,'']) output += createuserselect(id_user=id_user, action="adduserrole", step=3, users=users, extrausers=extrausers, button="add this user", id_role=id_role, email_user_pattern=email_user_pattern) try: id_user = int(id_user) except ValueError: pass # user selected already connected to role if id_user < 0: output += '

    users in brackets are already attached to the role, try another one...

    ' # a user is selected elif email_out: subtitle = "step 4 - confirm to add user" output += createhiddenform(action="adduserrole", text='add user %s to role %s?' % (email_out, name_role), id_role=id_role, email_user_pattern=email_user_pattern, id_user=id_user, confirm=1) # it is confirmed that this user should be added if confirm: # add user result = acca.acc_add_user_role(id_user=id_user, id_role=id_role) if result and result[2]: subtitle = 'step 5 - confirm user added' output += '

    confirm: user %s added to role %s.

    ' % (email_out, name_role) else: subtitle = 'step 5 - user could not be added' output += '

    sorry, but user could not be added.

    ' extra = """
    Create new role
    go here to add a new role.
    """ if str(id_role) != "0": extra += """
    Remove users
    remove users from role %s.
    Connected users
    show all users connected to role %s.
    Add authorization
    start adding new authorizations to role %s.
    """ % (id_role, name_role, id_role, name_role, id_role, name_role) return index(req=req, title=title, subtitle=subtitle, body=[output, extra], adminarea=3) def perform_addroleuser(req, email_user_pattern='', id_user='0', id_role='0', confirm=0): """delete connection between role and user. id_role - id of role to disconnect id_user - id of user to disconnect. """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) email_out = acca.acc_get_user_email(id_user=id_user) name_role = acca.acc_get_role_name(id_role=id_role) # used to sort roles, and also to determine right side links con_roles = [] not_roles = [] title = 'Connect user to roles' subtitle = 'step 1 - search for users' # clean email search string email_user_pattern = cleanstring_email(email_user_pattern) text = ' 1. search for user \n' text += ' \n' % (email_user_pattern, ) output = createhiddenform(action='addroleuser', text=text, button='search for users', id_role=id_role) if email_user_pattern: subtitle = 'step 2 - select user' try: users1 = run_sql("""SELECT id, email FROM user WHERE email<>'' AND email RLIKE %s ORDER BY email """, (email_user_pattern, )) except OperationalError: users1 = () users = [] for (id, email) in users1: users.append([id, email, '']) # no users if not users: output += '

    no qualified users, try new search.

    ' # too many users elif len(users) > MAXSELECTUSERS: output += '

    %s hits, too many qualified users, specify more narrow search. (limit %s)

    ' % (len(users), MAXSELECTUSERS) # ok number of users else: output += createuserselect(id_user=id_user, action='addroleuser', step=2, users=users, button='select user', email_user_pattern=email_user_pattern) if int(id_user): subtitle = 'step 3 - select role' # roles the user is connected to role_ids = acca.acc_get_user_roles(id_user=id_user) # all the roles, lists are sorted on the background of these... all_roles = acca.acc_get_all_roles() # sort the roles in connected and not connected roles for (id, name, description, dummy, dummy) in all_roles: if id in role_ids: con_roles.append([-id, name, description]) else: not_roles.append([id, name, description]) # create roleselect output += createroleselect(id_role=id_role, action='addroleuser', step=3, roles=not_roles, extraroles=con_roles, extrastamp='(connected)', button='add this role', email_user_pattern=email_user_pattern, id_user=id_user) if int(id_role) < 0: name_role = acca.acc_get_role_name(id_role=-int(id_role)) output += '

    role %s already connected to the user, try another one...

    ' % (name_role, ) elif int(id_role): subtitle = 'step 4 - confirm to add role to user' output += createhiddenform(action='addroleuser', text='add role %s to user %s?' % (name_role, email_out), email_user_pattern=email_user_pattern, id_user=id_user, id_role=id_role, confirm=1) if confirm: # add role result = acca.acc_add_user_role(id_user=id_user, id_role=id_role) if result and result[2]: subtitle = 'step 5 - confirm role added' output += '

    confirm: role %s added to user %s.

    ' % (name_role, email_out) else: subtitle = 'step 5 - role could not be added' output += '

    sorry, but role could not be added

    ' extra = """
    Create new role
    go here to add a new role.
    """ if int(id_user) and con_roles: extra += """
    Remove roles
    disconnect roles from user %s.
    """ % (id_user, email_out) if int(id_role): if int(id_role) < 0: id_role = -int(id_role) extra += """
    Remove users
    disconnect users from role %s.
    """ % (id_role, name_role) return index(req=req, title=title, subtitle=subtitle, body=[output, extra], adminarea=5) def perform_deleteuserrole(req, id_role='0', id_user='0', reverse=0, confirm=0): """delete connection between role and user. id_role - id of role to disconnect id_user - id of user to disconnect. """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) title = 'Remove user from role' email_user = acca.acc_get_user_email(id_user=id_user) name_role = acca.acc_get_role_name(id_role=id_role) output = '' if reverse in [0, '0']: adminarea = 3 subtitle = 'step 1 - select the role' output += createroleselect(id_role=id_role, action="deleteuserrole", step=1, roles=acca.acc_get_all_roles()) if id_role != "0": subtitle = 'step 2 - select the user' output += createuserselect(id_user=id_user, action="deleteuserrole", step=2, users=acca.acc_get_role_users(id_role=id_role), id_role=id_role) else: adminarea = 5 # show only if user is connected to a role, get users connected to roles users = run_sql("""SELECT DISTINCT(u.id), u.email, u.note FROM user u LEFT JOIN user_accROLE ur ON u.id = ur.id_user WHERE ur.id_accROLE != 'NULL' AND u.email != '' ORDER BY u.email """) has_roles = 1 # check if the user is connected to any roles for (id, email, note) in users: if str(id) == str(id_user): break # user not connected to a role else: subtitle = 'step 1 - user not connected' output += '

    no need to remove roles from user %s,
    user is not connected to any roles.

    ' % (email_user, ) has_roles, id_user = 0, '0' # stop the rest of the output below... # user connected to roles if has_roles: output += createuserselect(id_user=id_user, action="deleteuserrole", step=1, users=users, reverse=reverse) if id_user != "0": subtitle = 'step 2 - select the role' role_ids = acca.acc_get_user_roles(id_user=id_user) all_roles = acca.acc_get_all_roles() roles = [] for (id, name, desc, dummy, dummy) in all_roles: if id in role_ids: roles.append([id, name, desc]) output += createroleselect(id_role=id_role, action="deleteuserrole", step=2, roles=roles, id_user=id_user, reverse=reverse) if id_role != '0' and id_user != '0': subtitle = 'step 3 - confirm delete of user' output += createhiddenform(action="deleteuserrole", text='delete user %s from %s?' % (headerstrong(user=id_user), headerstrong(role=id_role)), id_role=id_role, id_user=id_user, reverse=reverse, confirm=1) if confirm: res = acca.acc_delete_user_role(id_user=id_user, id_role=id_role) if res: subtitle = 'step 4 - confirm delete of user' output += '

    confirm: deleted user %s from role %s.

    ' % (email_user, name_role) else: subtitle = 'step 4 - user could not be deleted' output += 'sorry, but user could not be deleted
    user is probably already deleted.' extra = '' if str(id_role) != "0": extra += """
    Connect user
    add users to role %s.
    """ % (id_role, name_role) if int(reverse): extra += """
    Remove user
    remove users from role %s.
    """ % (id_role, name_role) extra += '
    ' if str(id_user) != "0": extra += """
    Connect role
    add roles to user %s.
    """ % (email_user, id_user, email_user) if not int(reverse): extra += """
    Remove role
    remove roles from user %s.
    """ % (id_user, email_user, email_user) extra += '
    ' if extra: body = [output, extra] else: body = [output] return index(req=req, title=title, subtitle=subtitle, body=body, adminarea=adminarea) def perform_showuserdetails(req, id_user=0): """show the details of a user. """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) if id_user not in [0, '0']: output = userdetails(id_user=id_user) email_user = acca.acc_get_user_email(id_user=id_user) extra = """
    Connect role
    connect a role to user %s.
    Remove role
    remove a role from user %s.
    """ % (id_user, email_user, email_user, id_user, email_user) body = [output, extra] else: body = ['

    no details to show

    '] return index(req=req, title='Show User Details', subtitle='show user details', body=body, adminarea=5) def userdetails(id_user=0): """create the string to show details about a user. """ # find necessary details email_user = acca.acc_get_user_email(id_user=id_user) userroles = acca.acc_get_user_roles(id_user=id_user) conn_roles = [] # find connected roles for (id, name, desc, dummy, dummy) in acca.acc_get_all_roles(): if id in userroles: conn_roles.append([id, name, desc]) conn_roles[-1].append('show details' % (id, )) if conn_roles: # print details details = '

    roles connected to user %s

    ' % (email_user, ) details += tupletotable(header=['id', 'name', 'description', ''], tuple=conn_roles) else: details = '

    no roles connected to user %s.

    ' % (email_user, ) return details def perform_addauthorization(req, id_role="0", id_action="0", optional=0, reverse="0", confirm=0, **keywords): """ form to add new connection between user and role: id_role - role to connect id_action - action to connect reverse - role or action first? """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) # values that might get used name_role = acca.acc_get_role_name(id_role=id_role) or id_role name_action = acca.acc_get_action_name(id_action=id_action) or id_action optional = optional == 'on' and 1 or int(optional) extra = """
    Create new role
    go here to add a new role.
    """ # create the page according to which step the user is on # role -> action -> arguments if reverse in ["0", 0]: adminarea = 3 subtitle = 'step 1 - select role' output = createroleselect(id_role=id_role, action="addauthorization", step=1, roles=acca.acc_get_all_roles(), reverse=reverse) if str(id_role) != "0": subtitle = 'step 2 - select action' rolacts = acca.acc_get_role_actions(id_role) allhelp = acca.acc_get_all_actions() allacts = [] for r in allhelp: if r not in rolacts: allacts.append(r) output += createactionselect(id_action=id_action, action="addauthorization", step=2, actions=rolacts, extraactions=allacts, id_role=id_role, reverse=reverse) # action -> role -> arguments else: adminarea = 4 subtitle = 'step 1 - select action' output = createactionselect(id_action=id_action, action="addauthorization", step=1, actions=acca.acc_get_all_actions(), reverse=reverse) if str(id_action) != "0": subtitle = 'step 2 - select role' actroles = acca.acc_get_action_roles(id_action) allhelp = acca.acc_get_all_roles() allroles = [] for r in allhelp: if r not in actroles: allroles.append(r) output += createroleselect(id_role=id_role, action="addauthorization", step=2, roles=actroles, extraroles=allroles, id_action=id_action, reverse=reverse) # ready for step 3 no matter which direction we took to get here if id_action != "0" and id_role != "0": # links to adding authorizations in the other direction if str(reverse) == "0": extra += """
    Add authorization
    add authorizations to action %s.
    """ % (id_action, name_action) else: extra += """
    Add authorization
    add authorizations to role %s.
    """ % (id_role, name_role) subtitle = 'step 3 - enter values for the keywords\n' output += """
    """ % (id_role, id_action, reverse) # the actions argument keywords res_keys = acca.acc_get_action_keywords(id_action=id_action) # res used to display existing authorizations # res used to determine if showing "create connection without arguments" res_auths = acca.acc_find_possible_actions(id_role, id_action) if not res_keys: # action without arguments if not res_auths: output += """ create connection between %s?
    """ % (headerstrong(role=name_role, action=name_action, query=0), ) else: output += '

    connection without arguments is already created.

    ' else: # action with arguments optionalargs = acca.acc_get_action_is_optional(id_action=id_action) output += '3. authorized arguments
    ' if optionalargs: # optional arguments output += """

    connect %s to %s for any arguments
    connect %s to %s for only these argument cases:

    """ % (optional and 'checked="checked"' or '', name_role, name_action, not optional and 'checked="checked"' or '', name_role, name_action) # list the arguments allkeys = 1 for key in res_keys: output += '%s \n \n' output += '\n' # ask for confirmation if str(allkeys) != "0" or optional: keys = keywords.keys() keys.reverse() subtitle = 'step 4 - confirm add of authorization\n' text = """ create connection between
    %s
    """ % (headerstrong(role=name_role, action=name_action, query=0), ) if optional: text += 'withouth arguments' keywords = {} else: for key in keys: text += '%s: %s \n' % (escape(key), escape(keywords[key])) output += createhiddenform(action="addauthorization", text=text, id_role=id_role, id_action=id_action, reverse=reverse, confirm=1, optional=optional, **keywords) # show existing authorizations, found authorizations further up in the code... # res_auths = acca.acc_find_possible_actions(id_role, id_action) output += '

    existing authorizations:

    ' if res_auths: output += tupletotable(header=res_auths[0], tuple=res_auths[1:]) # shortcut to modifying authorizations extra += """
    Modify authorizations
    modify the existing authorizations.
    """ % (id_role, id_action, reverse) else: output += '

    no details to show

    ' # user confirmed to add entries if confirm: subtitle = 'step 5 - confirm authorization added' res1 = acca.acc_add_authorization(name_role=name_role, name_action=name_action, optional=optional, **keywords) if res1: res2 = acca.acc_find_possible_actions(id_role, id_action) arg = res1[0][3] # the arglistid new = [res2[0]] for row in res2[1:]: if int(row[0]) == int(arg): new.append(row) newauths = tupletotable(header=new[0], tuple=new[1:]) newentries = tupletotable(header=['role id', 'action id', 'argument id', '#'], tuple=res1) st = 'style="vertical-align: top"' output += """

    new authorization and entries:

    %s %s
    """ % (st, newauths, st, newentries) else: output += '

    sorry, authorization could not be added,
    it probably already exists

    ' # trying to put extra link on the right side try: body = [output, extra] except NameError: body = [output] return index(req=req, title = 'Create entry for new authorization', subtitle=subtitle, body=body, adminarea=adminarea) def perform_deleteroleaction(req, id_role="0", id_action="0", reverse=0, confirm=0): """delete all connections between a role and an action. id_role - id of the role id_action - id of the action reverse - 0: ask for role first 1: ask for action first""" (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) title = 'Remove action from role ' if reverse in ["0", 0]: # select role -> action adminarea = 3 subtitle = 'step 1 - select a role' output = createroleselect(id_role=id_role, action="deleteroleaction", step=1, roles=acca.acc_get_all_roles(), reverse=reverse) if id_role != "0": rolacts = acca.acc_get_role_actions(id_role=id_role) subtitle = 'step 2 - select the action' output += createactionselect(id_action=id_action, action="deleteroleaction", step=2, actions=rolacts, reverse=reverse, id_role=id_role, button="remove connection and all authorizations") else: # select action -> role adminarea = 4 subtitle = 'step 1 - select an action' output = createactionselect(id_action=id_action, action="deleteroleaction", step=1, actions=acca.acc_get_all_actions(), reverse=reverse) if id_action != "0": actroles = acca.acc_get_action_roles(id_action=id_action) subtitle = 'step 2 - select the role' output += createroleselect(id_role=id_role, action="deleteroleaction", step=2, roles=actroles, button="remove connection and all authorizations", id_action=id_action, reverse=reverse) if id_action != "0" and id_role != "0": subtitle = 'step 3 - confirm to remove authorizations' # ask for confirmation res = acca.acc_find_possible_actions(id_role, id_action) if res: output += '

    authorizations that will be deleted:

    ' output += tupletotable(header=res[0], tuple=res[1:]) output += createhiddenform(action="deleteroleaction", text='remove %s from %s' % (headerstrong(action=id_action), headerstrong(role=id_role)), confirm=1, id_role=id_role, id_action=id_action, reverse=reverse) else: output += 'no authorizations' # confirmation is given if confirm: subtitle = 'step 4 - confirm authorizations removed ' res = acca.acc_delete_role_action(id_role=id_role, id_action=id_action) if res: output += '

    confirm: removed %s from %s
    ' % (headerstrong(action=id_action), headerstrong(role=id_role)) output += '%s entries were removed.

    ' % (res, ) else: output += '

    sorry, no entries could be removed.

    ' return index(req=req, title=title, subtitle=subtitle, body=[output], adminarea=adminarea) def perform_modifyauthorizations(req, id_role="0", id_action="0", reverse=0, confirm=0, errortext='', sel='', authids=[]): """given ids of a role and an action, show all possible action combinations with checkboxes and allow user to access other functions. id_role - id of the role id_action - id of the action reverse - 0: ask for role first 1: ask for action first sel - which button and modification that is selected errortext - text to print when no connection exist between role and action authids - ids of checked checkboxes """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) name_role = acca.acc_get_role_name(id_role) name_action = acca.acc_get_action_name(id_action) output = '' try: id_role, id_action, reverse = int(id_role), int(id_action), int(reverse) except ValueError: pass extra = """
    Create new role
    go here to add a new role.
    """ if id_role or id_action: extra += '\n
    \n' if id_role and id_action: extra += """
    Add authorizations
    add an authorization to the existing ones.
    """ % (id_role, id_action, reverse) if id_role: extra += """
    Add authorizations
    add to role %s.
    """ % (id_role, name_role) if id_action: extra += """
    Add authorizations
    add to action %s.
    """ % (id_action, name_action) extra += '\n
    \n' if not reverse: # role -> action adminarea = 3 subtitle = 'step 1 - select the role' output += createroleselect(id_role=str(id_role), action="modifyauthorizations", step=1, roles=acca.acc_get_all_roles(), reverse=reverse) if id_role: rolacts = acca.acc_get_role_actions(id_role=id_role) subtitle = 'step 2 - select the action' output += createactionselect(id_action=str(id_action), action="modifyauthorizations", step=2, actions=rolacts, id_role=id_role, reverse=reverse) else: adminarea = 4 # action -> role subtitle = 'step 1 - select the action' output += createactionselect(id_action=str(id_action), action="modifyauthorizations", step=1, actions=acca.acc_get_all_actions(), reverse=reverse) if id_action: actroles = acca.acc_get_action_roles(id_action=id_action) subtitle = 'step 2 - select the role' output += createroleselect(id_role=str(id_role), action="modifyauthorizations", step=2, roles=actroles, id_action=id_action, reverse=reverse) if errortext: output += '

    %s

    ' % (errortext, ) if id_role and id_action: # adding to main area if type(authids) is not list: authids = [authids] subtitle = 'step 3 - select groups and modification' # get info res = acca.acc_find_possible_actions(id_role, id_action) # clean the authids hiddenids = [] if sel in ['delete selected']: hiddenids = authids[:] elif sel in ['split groups', 'merge groups']: for authid in authids: arghlp = res[int(authid)][0] if authid not in hiddenids and arghlp not in [-1, '-1', 0, '0']: hiddenids.append(authid) authids = hiddenids[:] if confirm: # do selected modification and output with new authorizations if sel == 'split groups': res = splitgroups(id_role, id_action, authids) elif sel == 'merge groups': res = mergegroups(id_role, id_action, authids) elif sel == 'delete selected': res = deleteselected(id_role, id_action, authids) authids = [] res = acca.acc_find_possible_actions(id_role, id_action) output += 'authorizations after %s.
    \n' % (sel, ) elif sel and authids: output += 'confirm choice of authorizations and modification.
    \n' else: output += 'select authorizations and perform modification.
    \n' if not res: errortext = 'all connections deleted, try different ' if reverse in ["0", 0]: return perform_modifyauthorizations(req=req, id_role=id_role, errortext=errortext + 'action.') else: return perform_modifyauthorizations(req=req, id_action=id_action, reverse=reverse, errortext=errortext + 'role.') # display output += modifyauthorizationsmenu(id_role, id_action, header=res[0], tuple=res[1:], checked=authids, reverse=reverse) if sel and authids: subtitle = 'step 4 - confirm to perform modification' # form with hidden authids output += '
    \n' % ('modifyauthorizations', ) for hiddenid in hiddenids: output += '\n' % (hiddenid, ) # choose what to do if sel == 'split groups': output += '

    split groups containing:

    ' elif sel == 'merge groups': output += '

    merge groups containing:

    ' elif sel == 'delete selected': output += '

    delete selected entries:

    ' extracolumn = '\n' extracolumn += '\n' # show the entries here... output += tupletotable_onlyselected(header=res[0], tuple=res[1:], selected=hiddenids, extracolumn=extracolumn) output += '\n' \ % (id_role, ) output += '\n' \ % (id_action, ) output += '\n' \ % (sel, ) output += '\n' \ % (reverse, ) output += '
    ' # tried to perform modification without something selected elif sel and not authids and not confirm: output += '

    no valid groups selected

    ' # trying to put extra link on the right side try: body = [output, extra] except NameError: body = [output] # Display the page return index(req=req, title='Modify Authorizations', subtitle=subtitle, body=body, adminarea=adminarea) def modifyauthorizationsmenu(id_role, id_action, tuple=[], header=[], checked=[], reverse=0): """create table with header and checkboxes, used for multiple choice. makes use of tupletotable to add the actual table id_role - selected role, hidden value in the form id_action - selected action, hidden value in the form tuple - all rows to be put in the table (with checkboxes) header - column headers, empty strings added at start and end checked - ids of rows to be checked """ if not tuple: return 'no authorisations...' argnum = len(acca.acc_get_action_keywords(id_action=id_action)) tuple2 = [] for t in tuple: tuple2.append(t[:]) tuple2 = addcheckboxes(datalist=tuple2, name='authids', startindex=1, checked=checked) hidden = ' \n' \ % (id_role, ) hidden += ' \n' \ % (id_action, ) hidden += ' \n' \ % (reverse, ) button = '\n' if argnum > 1: button += '\n' button += '\n' hdrstr = '' for h in [''] + header + ['']: hdrstr += ' %s\n' % (h, ) if hdrstr: hdrstr = ' \n%s\n \n' % (hdrstr, ) output = '
    \n' output += ' \n' output += hdrstr output += '\n' % (hidden, ) align = ['admintdleft'] * len(tuple2[0]) try: align[1] = 'admintdright' except IndexError: pass output += '' for i in range(len(tuple2[0])): output += '\n' % (align[i], tuple2[0][i]) output += '\n' \ % (len(tuple2), button) output += '\n' for row in tuple2[1:]: output += ' \n' for i in range(len(row)): output += '\n' % (align[i], row[i]) output += ' \n' output += '
    %s
    %s\n%s\n
    %s
    \n
    \n' return output def splitgroups(id_role=0, id_action=0, authids=[]): """get all the old ones, gather up the arglistids find a list of arglistidgroups to be split, unique get all actions in groups outside of the old ones, (old arglistid is allowed). show them like in showselect. """ if not id_role or not id_action or not authids: return 0 # find all the actions datalist = acca.acc_find_possible_actions(id_role, id_action) if type(authids) is str: authids = [authids] for i in range(len(authids)): authids[i] = int(authids[i]) # argumentlistids of groups to be split splitgrps = [] for authid in authids: hlp = datalist[authid][0] if hlp not in splitgrps and authid in range(1, len(datalist)): splitgrps.append(hlp) # split groups and return success or failure result = 1 for splitgroup in splitgrps: result = 1 and acca.acc_split_argument_group(id_role, id_action, splitgroup) return result def mergegroups(id_role=0, id_action=0, authids=[]): """get all the old ones, gather up the argauthids find a list of arglistidgroups to be split, unique get all actions in groups outside of the old ones, (old arglistid is allowed). show them like in showselect.""" if not id_role or not id_action or not authids: return 0 datalist = acca.acc_find_possible_actions(id_role, id_action) if type(authids) is str: authids = [authids] for i in range(len(authids)): authids[i] = int(authids[i]) # argumentlistids of groups to be merged mergegroups = [] for authid in authids: hlp = datalist[authid][0] if hlp not in mergegroups and authid in range(1, len(datalist)): mergegroups.append(hlp) # merge groups and return success or failure if acca.acc_merge_argument_groups(id_role, id_action, mergegroups): return 1 else: return 0 def deleteselected(id_role=0, id_action=0, authids=[]): """delete checked authorizations/possible actions, ids in authids. id_role - role to delete from id_action - action to delete from authids - listids for which possible actions to delete.""" if not id_role or not id_action or not authids: return 0 if type(authids) in [str, int]: authids = [authids] for i in range(len(authids)): authids[i] = int(authids[i]) result = acca.acc_delete_possible_actions(id_role=id_role, id_action=id_action, authids=authids) return result def headeritalic(**ids): """transform keyword=value pairs to string with value in italics. **ids - a dictionary of pairs to create string from """ output = '' value = '' table = '' for key in ids.keys(): if key in ['User', 'user']: value, table = 'email', 'user' elif key in ['Role', 'role']: value, table = 'name', 'accROLE' elif key in ['Action', 'action']: value, table = 'name', 'accACTION' else: if output: output += ' and ' output += ' %s %s' % (key, ids[key]) continue res = run_sql("""SELECT %%s FROM %s WHERE id = %%s""" % table, (value, ids[key])) if res: if output: output += ' and ' output += ' %s %s' % (key, res[0][0]) return output def headerstrong(query=1, **ids): """transform keyword=value pairs to string with value in strong text. **ids - a dictionary of pairs to create string from query - 1 -> try to find names to ids of role, user and action. 0 -> do not try to find names, use the value passed on """ output = '' value = '' table = '' for key in ids.keys(): if key in ['User', 'user']: value, table = 'email', 'user' elif key in ['Role', 'role']: value, table = 'name', 'accROLE' elif key in ['Action', 'action']: value, table = 'name', 'accACTION' else: if output: output += ' and ' output += ' %s %s' % (key, ids[key]) continue if query: res = run_sql("""SELECT %%s FROM %s WHERE id = %%s""" % table, (value, ids[key])) if res: if output: output += ' and ' output += ' %s %s' % (key, res[0][0]) else: if output: output += ' and ' output += ' %s %s' % (key, ids[key]) return output def startpage(): """create the menu for the startpage""" body = """
    selection for WebAccess Admin
    Role Area
    main area to configure administration rights and authorization rules.
    Action Area
    configure administration rights with the actions as starting point.
    User Area
    configure administration rights with the users as starting point.
    Reset Area
    reset roles, actions and authorizations.
    Manage accounts Area
    manage user accounts.
    Delegate Rights - With Restrictions
    delegate your rights for some roles.
    +
    Manage Robot Login
    +
    Manage robot login keys and test URLs
    """ return body def rankarea(): return "Rankmethod area" def perform_simpleauthorization(req, id_role=0, id_action=0): """show a page with simple overview of authorizations between a connected role and action. """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) res = acca.acc_find_possible_actions(id_role, id_action) if res: extra = createhiddenform(action='modifyauthorizations', button='modify authorizations', id_role=id_role, id_action=id_action) output = '

    authorizations for %s:

    ' \ % (headerstrong(action=id_action, role=id_role), ) output += tupletotable(header=res[0], tuple=res[1:], extracolumn=extra) else: output = 'no details to show' return index(req=req, title='Simple authorization details', subtitle='simple authorization details', body=[output], adminarea=3) def perform_showroleusers(req, id_role=0): """show a page with simple overview of a role and connected users. """ (auth_code, auth_message) = is_adminuser(req) if auth_code != 0: return mustloginpage(req, auth_message) res = acca.acc_get_role_users(id_role=id_role) name_role = acca.acc_get_role_name(id_role=id_role) if res: users = [] for (role_id, name, dummy) in res: users.append([role_id, name, 'show user details' % (role_id, )]) output = '

    users connected to %s:

    ' \ % (headerstrong(role=id_role), ) output += tupletotable(header=['id', 'name', ''], tuple=users) else: output = 'no users connected to role %s' \ % (name_role, ) extra = """
    Connect user
    connect users to the role.
    """ % (id_role, ) return index(req=req, title='Users connected to role %s' % (name_role, ), subtitle='simple details', body=[output, extra], adminarea=3) def createselect(id_input="0", label="", step=0, name="", action="", list=[], extralist=[], extrastamp='', button="", **hidden): """create form with select and hidden values id - the one to choose as selected if exists label - label shown to the left of the select name - the name of the select on which to reference it list - primary list to select from extralist - list of options to be put in paranthesis extrastamp - stamp extralist entries with this if not '' usually paranthesis around the entry button - the value/text to be put on the button **hidden - name=value pairs to be put as hidden in the form. """ step = step and '%s. ' % step or '' output = '
    \n' % (action, ) output += ' %s\n' % (step + label, ) output += ' \n' for key in hidden.keys(): output += ' \n' \ % (key, hidden[key]) output += ' \n' \ % (button, ) output += '
    \n' return output def createactionselect(id_action="0", label="select action", step=0, name="id_action", action="", actions=[], extraactions=[], extrastamp='', button="select action", **hidden): """create a select for roles in a form. see createselect.""" return createselect(id_input=id_action, label=label, step=step, name=name, action=action, list=actions, extralist=extraactions, extrastamp=extrastamp, button=button, **hidden) def createroleselect(id_role="0", label="select role", step=0, name="id_role", action="", roles=[], extraroles=[], extrastamp='', button="select role", **hidden): """create a select for roles in a form. see createselect.""" return createselect(id_input=id_role, label=label, step=step, name=name, action=action, list=roles, extralist=extraroles, extrastamp=extrastamp, button=button, **hidden) def createuserselect(id_user="0", label="select user", step=0, name="id_user", action="", users=[], extrausers=[], extrastamp='(connected)', button="select user", **hidden): """create a select for users in a form.see createselect.""" return createselect(id_input=id_user, label=label, step=step, name=name, action=action, list=users, extralist=extrausers, extrastamp=extrastamp, button=button, **hidden) def cleanstring(txt='', comma=0): """clean all the strings before submitting to access control admin. remove characters not letter, number or underscore, also remove leading underscores and numbers. return cleaned string. str - string to be cleaned comma - 1 -> allow the comma to divide multiple arguments 0 -> wash commas as well """ # remove not allowed characters txt = re.sub(r'[^a-zA-Z0-9_,]', '', txt) # split string on commas items = txt.split(',') txt = '' for item in items: if not item: continue if comma and txt: txt += ',' # create valid variable names txt += re.sub(r'^([0-9_])*', '', item) return txt def cleanstring_argumentvalue(txt=''): """clean the value of an argument before submitting it. allowed characters: a-z A-Z 0-9 _ * and space txt - string to be cleaned """ # remove not allowed characters txt = re.sub(r'[^a-zA-Z0-9_ *.]', '', txt) # trim leading and ending spaces txt = re.sub(r'^ *| *$', '', txt) return txt def cleanstring_email(txt=''): """clean the string and return a valid email address. txt - string to be cleaned """ # remove not allowed characters txt = re.sub(r'[^a-zA-Z0-9_.@-]', '', txt) return txt def check_email(txt=''): """control that submitted emails are correct. this little check is not very good, but better than nothing. """ r = re.compile(r'(.)+\@(.)+\.(.)+') return r.match(txt) and 1 or 0 def send_account_activated_message(account_email, send_to, password, ln=CFG_SITE_LANG): """Send an email to the address given by send_to about the new activated account.""" _ = gettext_set_language(ln) sub = _("Your account on '%s' has been activated") % CFG_SITE_NAME body = _("Your account earlier created on '%s' has been activated:") \ % CFG_SITE_NAME + '\n\n' body += ' ' + _("Username/Email:") + " %s\n" % account_email body += ' ' + _("Password:") + " %s\n" % ("*" * len(str(password))) body += "\n---------------------------------" body += "\n%s" % CFG_SITE_NAME return send_email(CFG_SITE_SUPPORT_EMAIL, send_to, sub, body, header='') def send_new_user_account_warning(new_account_email, send_to, password, ln=CFG_SITE_LANG): """Send an email to the address given by send_to about the new account new_account_email.""" _ = gettext_set_language(ln) sub = _("Account created on '%s'") % CFG_SITE_NAME body = _("An account has been created for you on '%s':") % CFG_SITE_NAME + '\n\n' body += ' ' + _("Username/Email:") + " %s\n" % new_account_email body += ' ' + _("Password:") + " %s\n" % ("*" * len(str(password))) body += "\n---------------------------------" body += "\n%s" % CFG_SITE_NAME return send_email(CFG_SITE_SUPPORT_EMAIL, send_to, sub, body, header='') def send_account_rejected_message(new_account_email, send_to, ln=CFG_SITE_LANG): """Send an email to the address given by send_to about the new account new_account_email.""" _ = gettext_set_language(ln) sub = _("Account rejected on '%s'") % CFG_SITE_NAME body = _("Your request for an account has been rejected on '%s':") \ % CFG_SITE_NAME + '\n\n' body += ' ' + _("Username/Email: %s") % new_account_email + "\n" body += "\n---------------------------------" body += "\n%s" % CFG_SITE_NAME return send_email(CFG_SITE_SUPPORT_EMAIL, send_to, sub, body, header='') def send_account_deleted_message(new_account_email, send_to, ln=CFG_SITE_LANG): """Send an email to the address given by send_to about the new account new_account_email.""" _ = gettext_set_language(ln) sub = _("Account deleted on '%s'") % CFG_SITE_NAME body = _("Your account on '%s' has been deleted:") % CFG_SITE_NAME + '\n\n' body += ' ' + _("Username/Email:") + " %s\n" % new_account_email body += "\n---------------------------------" body += "\n%s" % CFG_SITE_NAME return send_email(CFG_SITE_SUPPORT_EMAIL, send_to, sub, body, header='') def usage(exitcode=1, msg=""): """Prints usage info.""" if msg: print >> sys.stderr, "Error: %s." % msg print >> sys.stderr print >> sys.stderr, """Usage: %s [options] General options: -h, --help\t\tprint this help -V, --version\t\tprint version number Authentication options: -u, --user=USER\tUser name needed to perform the administrative task Option to administrate authorizations: -a, --add\t\tadd default authorization settings -c, --compile\t\tcompile firewall like role definitions (FireRole) -r, --reset\t\treset to default settings -D, --demo\t\tto be used with -a or -r in order to consider demo site authorizationss """ % sys.argv[0] sys.exit(exitcode) def main(): """Main function that analyzes command line input and calls whatever is appropriate. """ ## parse command line: # set user-defined options: options = {'user' : '', 'reset' : 0, 'compile' : 0, 'add' : 0, 'demo' : 0} try: opts, args = getopt.getopt(sys.argv[1:], "hVu:racD", ["help", "version", "user=", "reset", "add", "compile", "demo"]) except getopt.GetoptError, err: usage(1, err) try: for opt in opts: if opt[0] in ("-h", "--help"): usage(0) elif opt[0] in ("-V", "--version"): print __revision__ sys.exit(0) elif opt[0] in ("-u", "--user"): options["user"] = opt[1] elif opt[0] in ("-r", "--reset"): options["reset"] = 1 elif opt[0] in ("-a", "--add"): options["add"] = 1 elif opt[0] in ("-c", "--compile"): options["compile"] = 1 elif opt[0] in ("-D", "--demo"): options["demo"] = 1 else: usage(1) if options['add'] or options['reset'] or options['compile']: if acca.acc_get_action_id('cfgwebaccess'): # Action exists hence authentication works :-) options['user'] = authenticate(options['user'], authorization_msg="WebAccess Administration", authorization_action="cfgwebaccess") if options['reset'] and options['demo']: acca.acc_reset_default_settings([CFG_SITE_ADMIN_EMAIL], DEF_DEMO_USER_ROLES, DEF_DEMO_ROLES, DEF_DEMO_AUTHS) print "Reset default demo site settings." elif options['reset']: acca.acc_reset_default_settings([CFG_SITE_ADMIN_EMAIL]) print "Reset default settings." elif options['add'] and options['demo']: acca.acc_add_default_settings([CFG_SITE_ADMIN_EMAIL], DEF_DEMO_USER_ROLES, DEF_DEMO_ROLES, DEF_DEMO_AUTHS) print "Added default demo site settings." elif options['add']: acca.acc_add_default_settings([CFG_SITE_ADMIN_EMAIL]) print "Added default settings." if options['compile']: repair_role_definitions() print "Compiled firewall like role definitions." else: usage(1, "You must specify at least one command") except StandardError, e: usage(e) return ### okay, here we go: if __name__ == '__main__': main() diff --git a/modules/webaccess/web/admin/webaccessadmin.py b/modules/webaccess/web/admin/webaccessadmin.py index ca087852f..ec7a74f3e 100644 --- a/modules/webaccess/web/admin/webaccessadmin.py +++ b/modules/webaccess/web/admin/webaccessadmin.py @@ -1,345 +1,348 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Invenio WebAccess Administrator Interface.""" __revision__ = "$Id$" from invenio.config import CFG_SITE_LANG import invenio.webaccessadmin_lib as wal from invenio.access_control_config import CFG_ACC_EMPTY_ROLE_DEFINITION_SRC # reload(wal) # from invenio.webaccessadmin_lib import index index = wal.index def rolearea(req, grep='', ln=CFG_SITE_LANG): """create the role area menu page.""" return wal.perform_rolearea(req=req, grep=grep) def actionarea(req, grep='', ln=CFG_SITE_LANG): """create the role area menu page.""" return wal.perform_actionarea(req=req, grep=grep) +def managerobotlogin(req, robot_name='', new_pwd1='', new_pwd2='', login_method='', timeout='', referer='', ip='', action='', confirm=0, email='', groups='', nickname='', json_assertion='', url_only=0, ln=CFG_SITE_LANG): + """Manage robot login keys and test URL.""" + return wal.perform_managerobotlogin(req=req, robot_name=robot_name, new_pwd1=new_pwd1, new_pwd2=new_pwd2, login_method=login_method, timeout=timeout, referer=referer, ip=ip, action=action, confirm=confirm, email=email, groups=groups, nickname=nickname, json_assertion=json_assertion, url_only=url_only) def userarea(req, email_user_pattern='', ln=CFG_SITE_LANG): """create the user area menu page. """ return wal.perform_userarea(req=req, email_user_pattern=email_user_pattern) def listgroups(req, ln=CFG_SITE_LANG): return wal.perform_listgroups(req=req) def resetarea(req, ln=CFG_SITE_LANG): """create the role area menu page.""" return wal.perform_resetarea(req=req) def resetdefaultsettings(req, superusers=[], confirm=0, ln=CFG_SITE_LANG): """create the reset default settings page. """ return wal.perform_resetdefaultsettings(req=req, superusers=superusers, confirm=confirm) def adddefaultsettings(req, superusers=[], confirm=0, ln=CFG_SITE_LANG): """create the add default settings page. """ return wal.perform_adddefaultsettings(req=req, superusers=superusers, confirm=confirm) def manageaccounts(req, mtype='', content='', confirm=0, ln=CFG_SITE_LANG): """enable, disable and edit accounts""" return wal.perform_manageaccounts(req=req, mtype=mtype, content=content, confirm=confirm) def modifyaccountstatus(req, userID, email_user_pattern='', limit_to=-1, maxpage=25, page=1, callback='yes', confirm=0, ln=CFG_SITE_LANG): """enable or disable account""" return wal.perform_modifyaccountstatus(req=req, userID=userID, email_user_pattern=email_user_pattern, limit_to=limit_to, maxpage=maxpage, page=page, callback=callback, confirm=confirm) def modifypreferences(req, userID, login_method='', callback='yes', confirm=0, ln=CFG_SITE_LANG): """modify the preferences of an account""" return wal.perform_modifypreferences(req=req, userID=userID, login_method=login_method, callback=callback, confirm=confirm) def modifylogindata(req, userID, nickname='', email='', password='', callback='yes', confirm=0, ln=CFG_SITE_LANG): """modify the email/password of an account""" return wal.perform_modifylogindata(req=req, userID=userID, nickname=nickname, email=email, password=password, callback=callback, confirm=confirm) def rejectaccount(req, userID, email_user_pattern='', limit_to=-1, maxpage=25, page=1, callback='yes', confirm=0, ln=CFG_SITE_LANG): """Set account inactive, delete it and send email to the owner.""" return wal.perform_rejectaccount(req=req, userID=userID, email_user_pattern=email_user_pattern, limit_to=limit_to, maxpage=maxpage, page=page, callback=callback, confirm=confirm) def deleteaccount(req, userID, callback='yes', confirm=0, ln=CFG_SITE_LANG): """delete account""" return wal.perform_deleteaccount(req=req, userID=userID, callback=callback, confirm=confirm) def createaccount(req, email='', password='', callback='yes', confirm=0, ln=CFG_SITE_LANG): """create account""" return wal.perform_createaccount(req=req, email=email, password=password, callback=callback, confirm=confirm) def editaccount(req, userID, mtype='', content='', callback='yes', confirm=0, ln=CFG_SITE_LANG): """edit account. """ return wal.perform_editaccount(req=req, userID=userID, mtype=mtype, content=content, callback=callback, confirm=confirm) def becomeuser(req, userID='', callback='yes', confirm=0, ln=CFG_SITE_LANG): """edit account. """ return wal.perform_becomeuser(req=req, userID=userID, callback=callback, confirm=confirm) def modifyaccounts(req, email_user_pattern='', limit_to=-1, maxpage=25, page=1, callback='yes', confirm=0, ln=CFG_SITE_LANG): """Modify accounts. """ return wal.perform_modifyaccounts(req=req, email_user_pattern=email_user_pattern, limit_to=limit_to, maxpage=maxpage, page=page, callback=callback,confirm=confirm) def delegate_startarea(req, ln=CFG_SITE_LANG): """add info here""" return wal.perform_delegate_startarea(req=req) def delegate_adminsetup(req, id_role_admin=0, id_role_delegate=0, confirm=0, ln=CFG_SITE_LANG): """add info here""" return wal.perform_delegate_adminsetup(req=req, id_role_admin=id_role_admin, id_role_delegate=id_role_delegate, confirm=confirm) def delegate_adduserrole(req, id_role=0, email_user_pattern='', id_user=0, confirm=0, ln=CFG_SITE_LANG): """add info here""" return wal.perform_delegate_adduserrole(req=req, id_role=id_role, email_user_pattern=email_user_pattern, id_user=id_user, confirm=confirm) def delegate_deleteuserrole(req, id_role=0, id_user=0, confirm=0, ln=CFG_SITE_LANG): """add info here""" return wal.perform_delegate_deleteuserrole(req=req, id_role=id_role, id_user=id_user, confirm=confirm) def addrole(req, name_role='', description='put description here.', firerole_def_src=CFG_ACC_EMPTY_ROLE_DEFINITION_SRC, confirm=0, ln=CFG_SITE_LANG): """form to add a new role with these values: name_role - name of the new role description - optional description of the role firerole_def_src - optional firerole like definition """ return wal.perform_addrole(req=req, name_role=name_role, description=description, firerole_def_src=firerole_def_src, confirm=confirm) def modifyrole(req, id_role='0', name_role='', description='put description here.', firerole_def_src='', modified='0', confirm=0, ln=CFG_SITE_LANG): """form to add a new role with these values: name_role - name of the new role description - optional description of the role firerole_def_src - optional firerole like definition """ return wal.perform_modifyrole(req=req, id_role=id_role, name_role=name_role, description=description, firerole_def_src=firerole_def_src, modified=modified, confirm=confirm) def deleterole(req, id_role="0", confirm=0, ln=CFG_SITE_LANG): """select a role and show all connected information, users - users that can access the role. actions - actions with possible authorizations.""" return wal.perform_deleterole(req=req, id_role=id_role, confirm=confirm) def showroledetails(req, id_role='0', ln=CFG_SITE_LANG): """show the details of a role.""" return wal.perform_showroledetails(req=req, id_role=id_role) def showactiondetails(req, id_action="0", ln=CFG_SITE_LANG): """show the details of an action. """ return wal.perform_showactiondetails(req=req, id_action=id_action) def showuserdetails(req, id_user="0", ln=CFG_SITE_LANG): """show the details of an action. """ return wal.perform_showuserdetails(req=req, id_user=id_user) def adduserrole(req, id_role='0', email_user_pattern='', id_user='0', confirm=0, ln=CFG_SITE_LANG): """create connection between user and role. id_role - id of the role to add user to email_user_pattern - search for users using this pattern id_user - id of user to add to the role. """ return wal.perform_adduserrole(req=req, id_role=id_role, email_user_pattern=email_user_pattern, id_user=id_user, confirm=confirm) def addroleuser(req, email_user_pattern='', id_user='0', id_role='0', confirm=0, ln=CFG_SITE_LANG): """create connection between user and role. email_user_pattern - search for users using this pattern id_user - id of user to add to the role. id_role - id of the role to add user to. """ return wal.perform_addroleuser(req=req, email_user_pattern=email_user_pattern, id_user=id_user, id_role=id_role, confirm=confirm) def deleteuserrole(req, id_role='0', id_user='0', reverse=0, confirm=0, ln=CFG_SITE_LANG): """delete connection between role and user. id_role - id of role to disconnect id_user - id of user to disconnect. """ return wal.perform_deleteuserrole(req=req, id_role=id_role, id_user=id_user, reverse=reverse, confirm=confirm) def addauthorization(req, id_role="0", id_action="0", reverse="0", confirm=0, **keywords): """ form to add new connection between user and role: id_role - role to connect id_action - action to connect reverse - role or action first? """ return wal.perform_addauthorization(req=req, id_role=id_role, id_action=id_action, reverse=reverse, confirm=confirm, **keywords) def deleteroleaction(req, id_role="0", id_action="0", reverse=0, confirm=0, ln=CFG_SITE_LANG): """delete all connections between a role and an action. id_role - id of the role id_action - id of the action reverse - 0: ask for role first 1: ask for action first""" return wal.perform_deleteroleaction(req=req, id_role=id_role, id_action=id_action, reverse=reverse, confirm=confirm) def modifyauthorizations(req, id_role="0", id_action="0", reverse=0, confirm=0, sel='', errortext='', authids=[], ln=CFG_SITE_LANG): """given ids of a role and an action, show all possible action combinations with checkboxes and allow user to access other functions. id_role - id of the role id_action - id of the action reverse - 0: ask for role first 1: ask for action first sel - which button and modification that is selected errortext - text to print when no connection exist between role and action authids - ids of checked checkboxes """ return wal.perform_modifyauthorizations(req=req, id_role=id_role, id_action=id_action, reverse=reverse, confirm=confirm, sel=sel, authids=authids) def simpleauthorization(req, id_role=0, id_action=0, ln=CFG_SITE_LANG): """show a page with simple overview of authorizations between a connected role and action. """ return wal.perform_simpleauthorization(req=req, id_role=id_role, id_action=id_action) def showroleusers(req, id_role=0, ln=CFG_SITE_LANG): """show a page with simple overview of a role and connected users. """ return wal.perform_showroleusers(req=req, id_role=id_role) diff --git a/modules/webbasket/lib/webbasket_webinterface.py b/modules/webbasket/lib/webbasket_webinterface.py index 3e1edcee5..3b1b58ac9 100644 --- a/modules/webbasket/lib/webbasket_webinterface.py +++ b/modules/webbasket/lib/webbasket_webinterface.py @@ -1,1573 +1,1573 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """WebBasket Web Interface.""" __revision__ = "$Id$" __lastupdated__ = """$Date$""" from invenio import webinterface_handler_config as apache import os from invenio.config import CFG_SITE_URL, \ CFG_ACCESS_CONTROL_LEVEL_SITE, \ CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS, \ CFG_SITE_SECURE_URL, CFG_PREFIX, CFG_SITE_LANG from invenio.messages import gettext_set_language from invenio.webpage import page from invenio.webuser import getUid, page_not_authorized, isGuestUser from invenio.webbasket import \ check_user_can_comment, \ check_sufficient_rights, \ perform_request_display, \ perform_request_search, \ create_guest_warning_box, \ create_basket_navtrail, \ perform_request_write_note, \ perform_request_save_note, \ perform_request_delete_note, \ perform_request_add_group, \ perform_request_edit, \ perform_request_edit_topic, \ perform_request_list_public_baskets, \ perform_request_unsubscribe, \ perform_request_subscribe, \ perform_request_display_public, \ perform_request_write_public_note, \ perform_request_save_public_note, \ delete_record, \ move_record, \ perform_request_add, \ perform_request_create_basket, \ perform_request_delete, \ wash_topic, \ wash_group, \ perform_request_export_xml, \ page_start from invenio.webbasket_config import CFG_WEBBASKET_CATEGORIES, \ CFG_WEBBASKET_ACTIONS, \ CFG_WEBBASKET_SHARE_LEVELS from invenio.webbasket_dblayer import get_basket_name, \ get_max_user_rights_on_basket from invenio.urlutils import get_referer, redirect_to_url, make_canonical_urlargd from invenio.webinterface_handler import wash_urlargd, WebInterfaceDirectory from invenio.webstat import register_customevent from invenio.errorlib import register_exception from invenio.webuser import collect_user_info from invenio.webcomment import check_user_can_attach_file_to_comments from invenio.access_control_engine import acc_authorize_action try: from invenio.fckeditor_invenio_connector import FCKeditorConnectorInvenio fckeditor_available = True except ImportError, e: fckeditor_available = False from invenio.bibdocfile import stream_file class WebInterfaceBasketCommentsFiles(WebInterfaceDirectory): """Handle upload and access to files for comments in WebBasket. The upload is currently only available through the FCKeditor. """ def _lookup(self, component, path): """ This handler is invoked for the dynamic URLs (for getting and putting attachments) Eg: /yourbaskets/attachments/get/31/652/5/file/myfile.pdf /yourbaskets/attachments/get/31/552/5/image/myfigure.png bskid/recid/uid/ /yourbaskets/attachments/put/31/550/ bskid/recid """ if component == 'get' and len(path) > 4: bskid = path[0] # Basket id recid = path[1] # Record id uid = path[2] # uid of the submitter file_type = path[3] # file, image, flash or media (as # defined by FCKeditor) if file_type in ['file', 'image', 'flash', 'media']: file_name = '/'.join(path[4:]) # the filename def answer_get(req, form): """Accessing files attached to comments.""" form['file'] = file_name form['type'] = file_type form['uid'] = uid form['recid'] = recid form['bskid'] = bskid return self._get(req, form) return answer_get, [] elif component == 'put' and len(path) > 1: bskid = path[0] # Basket id recid = path[1] # Record id def answer_put(req, form): """Attaching file to a comment.""" form['recid'] = recid form['bskid'] = bskid return self._put(req, form) return answer_put, [] # All other cases: file not found return None, [] def _get(self, req, form): """ Returns a file attached to a comment. A file is attached to a comment of a record of a basket, by a user (who is the author of the comment), and is of a certain type (file, image, etc). Therefore these 5 values are part of the URL. Eg: CFG_SITE_URL/yourbaskets/attachments/get/31/91/5/file/myfile.pdf bskid/recid/uid """ argd = wash_urlargd(form, {'file': (str, None), 'type': (str, None), 'uid': (int, 0), 'bskid': (int, 0), 'recid': (int, 0)}) _ = gettext_set_language(argd['ln']) # Can user view this basket & record & comment, i.e. can user # access its attachments? #uid = getUid(req) user_info = collect_user_info(req) rights = get_max_user_rights_on_basket(argd['uid'], argd['bskid']) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) - if user_info['email'] == 'guest' and not user_info['apache_user']: + if user_info['email'] == 'guest': # Ask to login target = '/youraccount/login' + \ make_canonical_urlargd({'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target) elif not(check_sufficient_rights(rights, CFG_WEBBASKET_SHARE_LEVELS['READITM'])): return page_not_authorized(req, "../", \ text = _("You are not authorized to view this attachment")) if not argd['file'] is None: # Prepare path to file on disk. Normalize the path so that # ../ and other dangerous components are removed. path = os.path.abspath(CFG_PREFIX + '/var/data/baskets/comments/' + \ str(argd['bskid']) + '/' + str(argd['recid']) + '/' + \ str(argd['uid']) + '/' + argd['type'] + '/' + \ argd['file']) # Check that we are really accessing attachements # directory, for the declared basket and record. if path.startswith(CFG_PREFIX + '/var/data/baskets/comments/' + \ str(argd['bskid']) + '/' + str(argd['recid'])) and \ os.path.exists(path): return stream_file(req, path) # Send error 404 in all other cases return apache.HTTP_NOT_FOUND def _put(self, req, form): """ Process requests received from FCKeditor to upload files, etc. URL eg: CFG_SITE_URL/yourbaskets/attachments/put/31/91/ bskid/recid/ """ if not fckeditor_available: return argd = wash_urlargd(form, {'bskid': (int, 0), 'recid': (int, 0)}) uid = getUid(req) # URL where the file can be fetched after upload user_files_path = '%(CFG_SITE_URL)s/yourbaskets/attachments/get/%(bskid)s/%(recid)i/%(uid)s' % \ {'uid': uid, 'recid': argd['recid'], 'bskid': argd['bskid'], 'CFG_SITE_URL': CFG_SITE_URL} # Path to directory where uploaded files are saved user_files_absolute_path = '%(CFG_PREFIX)s/var/data/baskets/comments/%(bskid)s/%(recid)s/%(uid)s' % \ {'uid': uid, 'recid': argd['recid'], 'bskid': argd['bskid'], 'CFG_PREFIX': CFG_PREFIX} # Create a Connector instance to handle the request conn = FCKeditorConnectorInvenio(form, recid=argd['recid'], uid=uid, allowed_commands=['QuickUpload'], allowed_types = ['File', 'Image', 'Flash', 'Media'], user_files_path = user_files_path, user_files_absolute_path = user_files_absolute_path) # Check that user can # 1. is logged in # 2. comment records of this basket (to simplify, we use # WebComment function to check this, even if it is not # entirely adequate) # 3. attach files user_info = collect_user_info(req) (auth_code, dummy) = check_user_can_attach_file_to_comments(user_info, argd['recid']) - if user_info['email'] == 'guest' and not user_info['apache_user']: + if user_info['email'] == 'guest': # 1. User is guest: must login prior to upload data = conn.sendUploadResults(1, '', '', 'Please login before uploading file.') if not user_info['precached_usebaskets']: data = conn.sendUploadResults(1, '', '', 'Sorry, you are not allowed to use WebBasket') elif not check_user_can_comment(uid, argd['bskid']): # 2. User cannot edit comment of this basket data = conn.sendUploadResults(1, '', '', 'Sorry, you are not allowed to submit files') elif auth_code: # 3. User cannot submit data = conn.sendUploadResults(1, '', '', 'Sorry, you are not allowed to submit files.') else: # Process the upload and get the response data = conn.doResponse() # Transform the headers into something ok for mod_python for header in conn.headers: if not header is None: if header[0] == 'Content-Type': req.content_type = header[1] else: req.headers_out[header[0]] = header[1] # Send our response req.send_http_header() req.write(data) class WebInterfaceYourBasketsPages(WebInterfaceDirectory): """Defines the set of /yourbaskets pages.""" _exports = ['', 'display_item', 'display', 'search', 'write_note', 'save_note', 'delete_note', 'add', 'delete', 'modify', 'edit', 'edit_topic', 'create_basket', 'display_public', 'list_public_baskets', 'subscribe', 'unsubscribe', 'write_public_note', 'save_public_note', 'attachments'] attachments = WebInterfaceBasketCommentsFiles() def index(self, req, dummy): """Index page.""" redirect_to_url(req, '%s/yourbaskets/display?%s' % (CFG_SITE_URL, req.args)) def display_item(self, req, dummy): """Legacy URL redirection.""" redirect_to_url(req, '%s/yourbaskets/display?%s' % (CFG_SITE_URL, req.args)) def display(self, req, form): """Display basket interface.""" #import rpdb2; rpdb2.start_embedded_debugger('password', fAllowRemote=True) argd = wash_urlargd(form, {'category': (str, CFG_WEBBASKET_CATEGORIES['PRIVATE']), 'topic': (str, ""), 'group': (int, 0), 'bskid': (int, 0), 'recid': (int, 0), 'bsk_to_sort': (int, 0), 'sort_by_title': (str, ""), 'sort_by_date': (str, ""), 'of': (str, "hb"), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/display", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/display%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) (body, warnings, navtrail) = perform_request_display(uid=uid, selected_category=argd['category'], selected_topic=argd['topic'], selected_group_id=argd['group'], selected_bskid=argd['bskid'], selected_recid=argd['recid'], of=argd['of'], ln=argd['ln']) if isGuestUser(uid): body = create_guest_warning_box(argd['ln']) + body # register event in webstat if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["display", "", user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") if argd['of'] != 'hb': page_start(req, of=argd['of']) if argd['of'].startswith('x'): xml = perform_request_export_xml(body) return xml return page(title = _("Display baskets"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def search(self, req, form): """Search baskets interface.""" argd = wash_urlargd(form, {'category': (str, ""), 'topic': (str, ""), 'group': (int, 0), 'p': (str, ""), 'b': (str, ""), 'n': (int, 0), 'of': (str, "hb"), 'verbose': (int, 0), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/search", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/search%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) (body, warnings, navtrail) = perform_request_search(uid=uid, selected_category=argd['category'], selected_topic=argd['topic'], selected_group_id=argd['group'], p=argd['p'], b=argd['b'], n=argd['n'], #format=argd['of'], ln=argd['ln']) # register event in webstat if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["search", "", user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Search baskets"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def write_note(self, req, form): """Write a comment (just interface for writing)""" argd = wash_urlargd(form, {'category': (str, CFG_WEBBASKET_CATEGORIES['PRIVATE']), 'topic': (str, ""), 'group': (int, 0), 'bskid': (int, 0), 'recid': (int, 0), 'cmtid': (int, 0), 'of' : (str, ''), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/write_note", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/write_note%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) (body, warnings, navtrail) = perform_request_write_note(uid=uid, category=argd['category'], topic=argd['topic'], group_id=argd['group'], bskid=argd['bskid'], recid=argd['recid'], cmtid=argd['cmtid'], ln=argd['ln']) # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["write_note", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Add a note"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of']) def save_note(self, req, form): """Save comment on record in basket""" argd = wash_urlargd(form, {'category': (str, CFG_WEBBASKET_CATEGORIES['PRIVATE']), 'topic': (str, ""), 'group': (int, 0), 'bskid': (int, 0), 'recid': (int, 0), 'note_title': (str, ""), 'note_body': (str, ""), 'editor_type': (str, ""), 'of': (str, ''), 'ln': (str, CFG_SITE_LANG), 'reply_to': (int, 0)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/save_note", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/save_note%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) (body, warnings, navtrail) = perform_request_save_note(uid=uid, category=argd['category'], topic=argd['topic'], group_id=argd['group'], bskid=argd['bskid'], recid=argd['recid'], note_title=argd['note_title'], note_body=argd['note_body'], editor_type=argd['editor_type'], ln=argd['ln'], reply_to=argd['reply_to']) # TODO: do not stat event if save was not succussful # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["save_note", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Display item and notes"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def delete_note(self, req, form): """Delete a comment @param bskid: id of basket (int) @param recid: id of record (int) @param cmtid: id of comment (int) @param category: category (see webbasket_config) (str) @param topic: nb of topic currently displayed (int) @param group: id of group baskets currently displayed (int) @param ln: language""" argd = wash_urlargd(form, {'category': (str, CFG_WEBBASKET_CATEGORIES['PRIVATE']), 'topic': (str, ""), 'group': (int, 0), 'bskid': (int, 0), 'recid': (int, 0), 'cmtid': (int, 0), 'of' : (str, ''), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/delete_note", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/delete_note%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/display%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) (body, warnings, navtrail) = perform_request_delete_note(uid=uid, category=argd['category'], topic=argd['topic'], group_id=argd['group'], bskid=argd['bskid'], recid=argd['recid'], cmtid=argd['cmtid'], ln=argd['ln']) # TODO: do not stat event if delete was not succussful # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) user_info = collect_user_info(req) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["delete_note", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Display item and notes"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def add(self, req, form): """Add records to baskets. @param recid: list of records to add @param colid: in case of external collections, the id of the collection the records belong to @param bskids: list of baskets to add records to. if not provided, will return a page where user can select baskets @param referer: URL of the referring page @param new_basket_name: add record to new basket @param new_topic_name: new basket goes into new topic @param create_in_topic: # of topic to put basket into @param ln: language""" # TODO: apply a maximum limit of items (100) that can be added to a basket # at once. Also see the build_search_url function of websearch_..._searcher.py # for the "rg" GET variable. argd = wash_urlargd(form, {'recid': (list, []), 'category': (str, ""), 'bskid': (int, 0), 'colid': (int, 0), 'es_title': (str, ""), 'es_desc': (str, ""), 'es_url': (str, ""), 'note_body': (str, ""), 'editor_type': (str, ""), 'b': (str, ""), 'copy': (int, 0), 'referer': (str, ""), "of" : (str, ''), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/add", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/add%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) if not argd['referer']: argd['referer'] = get_referer(req) (body, warnings, navtrail) = perform_request_add(uid=uid, recids=argd['recid'], colid=argd['colid'], bskid=argd['bskid'], es_title=argd['es_title'], es_desc=argd['es_desc'], es_url=argd['es_url'], note_body=argd['note_body'], editor_type=argd['editor_type'], category=argd['category'], b=argd['b'], copy=argd['copy'], referer=argd['referer'], ln=argd['ln']) if isGuestUser(uid): body = create_guest_warning_box(argd['ln']) + body # register event in webstat bskid = argd['bskid'] basket_str = "%s (%s)" % (get_basket_name(bskid), bskid) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["add", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _('Add to basket'), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def delete(self, req, form): """Delete basket interface""" argd = wash_urlargd(form, {'bskid': (int, -1), 'confirmed': (int, 0), 'category': (str, CFG_WEBBASKET_CATEGORIES['PRIVATE']), 'topic': (str, ""), 'group': (int, 0), 'of' : (str, ''), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/delete", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/delete%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) (body, warnings)=perform_request_delete(uid=uid, bskid=argd['bskid'], confirmed=argd['confirmed'], category=argd['category'], selected_topic=argd['topic'], selected_group_id=argd['group'], ln=argd['ln']) if argd['confirmed']: if argd['category'] == CFG_WEBBASKET_CATEGORIES['PRIVATE']: argd['topic'] = wash_topic(uid, argd['topic'])[0] elif argd['category'] == CFG_WEBBASKET_CATEGORIES['GROUP']: argd['group'] = wash_group(uid, argd['group'])[0] url = """%s/yourbaskets/display?category=%s&topic=%s&group=%i&ln=%s""" % \ (CFG_SITE_URL, argd['category'], argd['topic'], argd['group'], argd['ln']) redirect_to_url(req, url) else: navtrail = ''\ '%s' navtrail %= (CFG_SITE_URL, argd['ln'], _("Your Account")) navtrail_end = create_basket_navtrail(uid=uid, category=argd['category'], topic=argd['topic'], group=argd['group'], bskid=argd['bskid'], ln=argd['ln']) if isGuestUser(uid): body = create_guest_warning_box(argd['ln']) + body # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["delete", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Delete a basket"), body = body, navtrail = navtrail + navtrail_end, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of']) def modify(self, req, form): """Modify basket content interface (reorder, suppress record, etc.)""" argd = wash_urlargd(form, {'action': (str, ""), 'bskid': (int, -1), 'recid': (int, 0), 'category': (str, CFG_WEBBASKET_CATEGORIES['PRIVATE']), 'topic': (str, ""), 'group': (int, 0), 'of' : (str, ''), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/modify", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/modify%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) url = CFG_SITE_URL url += '/yourbaskets/display?category=%s&topic=%s&group=%i&bskid=%i&ln=%s' % \ (argd['category'], argd['topic'], argd['group'], argd['bskid'], argd['ln']) if argd['action'] == CFG_WEBBASKET_ACTIONS['DELETE']: delete_record(uid, argd['bskid'], argd['recid']) redirect_to_url(req, url) elif argd['action'] == CFG_WEBBASKET_ACTIONS['UP']: move_record(uid, argd['bskid'], argd['recid'], argd['action']) redirect_to_url(req, url) elif argd['action'] == CFG_WEBBASKET_ACTIONS['DOWN']: move_record(uid, argd['bskid'], argd['recid'], argd['action']) redirect_to_url(req, url) elif argd['action'] == CFG_WEBBASKET_ACTIONS['COPY']: title = _("Copy record to basket") referer = get_referer(req) (body, warnings, navtrail) = perform_request_add(uid=uid, recids=argd['recid'], copy=True, referer=referer, ln=argd['ln']) if isGuestUser(uid): body = create_guest_warning_box(argd['ln']) + body else: title = '' body = '' warnings = [('WRN_WEBBASKET_UNDEFINED_ACTION',)] navtrail = ''\ '%s' navtrail %= (CFG_SITE_URL, argd['ln'], _("Your Account")) navtrail_end = create_basket_navtrail(uid=uid, category=argd['category'], topic=argd['topic'], group=argd['group'], bskid=argd['bskid'], ln=argd['ln']) # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["modify", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = title, body = body, navtrail = navtrail + navtrail_end, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of']) def edit(self, req, form): """Edit basket interface""" argd = wash_urlargd(form, {'bskid': (int, 0), 'groups': (list, []), 'topic': (str, ""), 'add_group': (str, ""), 'group_cancel': (str, ""), 'submit': (str, ""), 'cancel': (str, ""), 'delete': (str, ""), 'new_name': (str, ""), 'new_topic': (str, ""), 'new_topic_name': (str, ""), 'new_group': (str, ""), 'external': (str, ""), 'of' : (str, ''), 'ln': (str, CFG_SITE_LANG)}) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/edit", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/edit%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) _ = gettext_set_language(argd['ln']) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) if argd['cancel']: url = CFG_SITE_URL + '/yourbaskets/display?category=%s&topic=%s&ln=%s' url %= (CFG_WEBBASKET_CATEGORIES['PRIVATE'], argd['topic'], argd['ln']) redirect_to_url(req, url) elif argd['delete']: url = CFG_SITE_URL url += '/yourbaskets/delete?bskid=%i&category=%s&topic=%s&ln=%s' % \ (argd['bskid'], CFG_WEBBASKET_CATEGORIES['PRIVATE'], argd['topic'], argd['ln']) redirect_to_url(req, url) elif argd['add_group'] and not(argd['new_group']): body = perform_request_add_group(uid=uid, bskid=argd['bskid'], topic=argd['topic'], ln=argd['ln']) warnings = [] elif (argd['add_group'] and argd['new_group']) or argd['group_cancel']: if argd['add_group']: perform_request_add_group(uid=uid, bskid=argd['bskid'], topic=argd['topic'], group_id=argd['new_group'], ln=argd['ln']) (body, warnings) = perform_request_edit(uid=uid, bskid=argd['bskid'], topic=argd['topic'], ln=argd['ln']) elif argd['submit']: (body, warnings) = perform_request_edit(uid=uid, bskid=argd['bskid'], topic=argd['topic'], new_name=argd['new_name'], new_topic=argd['new_topic'], new_topic_name=argd['new_topic_name'], groups=argd['groups'], external=argd['external'], ln=argd['ln']) if argd['new_topic'] != "-1": argd['topic'] = argd['new_topic'] url = CFG_SITE_URL + '/yourbaskets/display?category=%s&topic=%s&ln=%s' % \ (CFG_WEBBASKET_CATEGORIES['PRIVATE'], argd['topic'], argd['ln']) redirect_to_url(req, url) else: (body, warnings) = perform_request_edit(uid=uid, bskid=argd['bskid'], topic=argd['topic'], ln=argd['ln']) navtrail = ''\ '%s' navtrail %= (CFG_SITE_URL, argd['ln'], _("Your Account")) navtrail_end = create_basket_navtrail( uid=uid, category=CFG_WEBBASKET_CATEGORIES['PRIVATE'], topic=argd['topic'], group=0, bskid=argd['bskid'], ln=argd['ln']) if isGuestUser(uid): body = create_guest_warning_box(argd['ln']) + body # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["edit", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Edit basket"), body = body, navtrail = navtrail + navtrail_end, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of']) def edit_topic(self, req, form): """Edit topic interface""" argd = wash_urlargd(form, {'topic': (str, ""), 'submit': (str, ""), 'cancel': (str, ""), 'delete': (str, ""), 'new_name': (str, ""), 'of' : (str, ''), 'ln': (str, CFG_SITE_LANG)}) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/edit", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/edit_topic%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) _ = gettext_set_language(argd['ln']) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) if argd['cancel']: url = CFG_SITE_URL + '/yourbaskets/display?category=%s&ln=%s' url %= (CFG_WEBBASKET_CATEGORIES['PRIVATE'], argd['ln']) #url = CFG_SITE_URL + '/yourbaskets/display?category=%s&topic=%s&ln=%s' #url %= (CFG_WEBBASKET_CATEGORIES['PRIVATE'], argd['topic'], # argd['ln']) redirect_to_url(req, url) elif argd['delete']: url = CFG_SITE_URL url += '/yourbaskets/delete?bskid=%i&category=%s&topic=%s&ln=%s' % \ (argd['bskid'], CFG_WEBBASKET_CATEGORIES['PRIVATE'], argd['topic'], argd['ln']) redirect_to_url(req, url) elif argd['submit']: f = open("/tmp/skata", "w") f.write("submit: " + argd['topic'] + " |+| " + argd['new_name'] + "\n") f.close() (body, warnings) = perform_request_edit_topic(uid=uid, topic=argd['topic'], new_name=argd['new_name'], ln=argd['ln']) #url = CFG_SITE_URL + '/yourbaskets/display?category=%s&topic=%s&ln=%s' % \ # (CFG_WEBBASKET_CATEGORIES['PRIVATE'], # argd['topic'], argd['ln']) url = CFG_SITE_URL + '/yourbaskets/display?category=%s&ln=%s' % \ (CFG_WEBBASKET_CATEGORIES['PRIVATE'], argd['ln']) redirect_to_url(req, url) else: f = open("/tmp/skata", "w") f.write("normal: " + argd['topic'] + " |+| " + argd['new_name'] + "\n") f.close() (body, warnings) = perform_request_edit_topic(uid=uid, topic=argd['topic'], ln=argd['ln']) navtrail = ''\ '%s' navtrail %= (CFG_SITE_URL, argd['ln'], _("Your Account")) navtrail_end = "" #navtrail_end = create_basket_navtrail( # uid=uid, # category=CFG_WEBBASKET_CATEGORIES['PRIVATE'], # topic=argd['topic'], # group=0, # ln=argd['ln']) if isGuestUser(uid): body = create_guest_warning_box(argd['ln']) + body # register event in webstat #basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) #if user_info['email']: # user_str = "%s (%d)" % (user_info['email'], user_info['uid']) #else: # user_str = "" #try: # register_customevent("baskets", ["edit", basket_str, user_str]) #except: # register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Edit topic"), body = body, navtrail = navtrail + navtrail_end, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of']) def create_basket(self, req, form): """Create basket interface""" argd = wash_urlargd(form, {'new_basket_name': (str, ""), 'new_topic_name' : (str, ""), 'create_in_topic': (str, "-1"), 'topic' : (str, ""), 'of' : (str, ''), 'ln' : (str, CFG_SITE_LANG)}) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/create_basket", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/create_basket%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) _ = gettext_set_language(argd['ln']) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) if argd['new_basket_name'] and \ (argd['new_topic_name'] or argd['create_in_topic'] != "-1"): topic = perform_request_create_basket( uid=uid, new_basket_name=argd['new_basket_name'], new_topic_name=argd['new_topic_name'], create_in_topic=argd['create_in_topic'], ln=argd['ln']) # register event in webstat basket_str = "%s ()" % argd['new_basket_name'] if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["create_basket", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") url = CFG_SITE_URL + '/yourbaskets/display?category=%s&topic=%s&ln=%s' url %= (CFG_WEBBASKET_CATEGORIES['PRIVATE'], topic, argd['ln']) redirect_to_url(req, url) else: (body, warnings) = perform_request_create_basket(uid=uid, new_basket_name=argd['new_basket_name'], new_topic_name=argd['new_topic_name'], create_in_topic=argd['create_in_topic'], topic=argd['topic'], ln=argd['ln']) navtrail = '%s' navtrail %= (CFG_SITE_URL, argd['ln'], _("Your Account")) if isGuestUser(uid): body = create_guest_warning_box(argd['ln']) + body return page(title = _("Create basket"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of']) def display_public(self, req, form): """Display a public basket""" argd = wash_urlargd(form, {'bskid': (int, 0), 'recid': (int, 0), 'of': (str, "hb"), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/display", navmenuid = 'yourbaskets') user_info = collect_user_info(req) if not argd['bskid']: (body, warnings, navtrail) = perform_request_list_public_baskets(uid) title = _('List of public baskets') # register event in webstat if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["list_public_baskets", "", user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") else: (body, warnings, navtrail) = perform_request_display_public(uid=uid, selected_bskid=argd['bskid'], selected_recid=argd['recid'], of=argd['of'], ln=argd['ln']) title = _('Public basket') # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["display_public", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") if argd['of'] == 'xm': page_start(req, of=argd['of']) return perform_request_export_xml(body) return page(title = title, body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def list_public_baskets(self, req, form): """List of public baskets interface.""" argd = wash_urlargd(form, {'limit': (int, 1), 'sort': (str, 'name'), 'asc': (int, 1), 'of': (str, ''), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE == 2: return page_not_authorized(req, "../yourbaskets/list_public_baskets", navmenuid = 'yourbaskets') user_info = collect_user_info(req) nb_views_show = acc_authorize_action(user_info, 'runwebstatadmin') nb_views_show_p = not(nb_views_show[0]) (body, warnings, navtrail) = perform_request_list_public_baskets(uid, argd['limit'], argd['sort'], argd['asc'], nb_views_show_p, argd['ln']) return page(title = _("List of public baskets"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def subscribe(self, req, form): """Subscribe to a basket pseudo-interface.""" argd = wash_urlargd(form, {'bskid': (int, 0), 'of': (str, 'hb'), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE == 2: return page_not_authorized(req, "../yourbaskets/subscribe", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/subscribe%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) if not argd['bskid']: (body, warnings, navtrail) = perform_request_list_public_baskets(uid) title = _('List of public baskets') else: # TODO: Take care of XML output as shown below #req.content_type = "text/xml" #req.send_http_header() #return perform_request_display_public(bskid=argd['bskid'], of=argd['of'], ln=argd['ln']) (subscribe_warnings_html, subscribe_warnings) = perform_request_subscribe(uid, argd['bskid'], argd['ln']) (body, warnings, navtrail) = perform_request_display_public(uid=uid, selected_bskid=argd['bskid'], selected_recid=0, of=argd['of'], ln=argd['ln']) warnings.extend(subscribe_warnings) body = subscribe_warnings_html + body title = _('Public basket') return page(title = title, body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def unsubscribe(self, req, form): """Unsubscribe from basket pseudo-interface.""" argd = wash_urlargd(form, {'bskid': (int, 0), 'of': (str, 'hb'), 'ln': (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE == 2: return page_not_authorized(req, "../yourbaskets/unsubscribe", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/unsubscribe%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) if not argd['bskid']: (body, warnings, navtrail) = perform_request_list_public_baskets(uid) title = _('List of public baskets') else: # TODO: Take care of XML output as shown below #req.content_type = "text/xml" #req.send_http_header() #return perform_request_display_public(bskid=argd['bskid'], of=argd['of'], ln=argd['ln']) (unsubscribe_warnings_html, unsubscribe_warnings) = perform_request_unsubscribe(uid, argd['bskid'], argd['ln']) (body, warnings, navtrail) = perform_request_display_public(uid=uid, selected_bskid=argd['bskid'], selected_recid=0, of=argd['of'], ln=argd['ln']) warnings.extend(unsubscribe_warnings) body = unsubscribe_warnings_html + body title = _('Public basket') return page(title = title, body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) def write_public_note(self, req, form): """Write a comment (just interface for writing)""" argd = wash_urlargd(form, {'bskid': (int, 0), 'recid': (int, 0), 'cmtid': (int, 0), 'of' : (str, ''), 'ln' : (str, CFG_SITE_LANG)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/write_public_note", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/write_public_note%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) (body, warnings, navtrail) = perform_request_write_public_note(uid=uid, bskid=argd['bskid'], recid=argd['recid'], cmtid=argd['cmtid'], ln=argd['ln']) # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["write_public_note", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Add a note"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of']) def save_public_note(self, req, form): """Save comment on record in basket""" argd = wash_urlargd(form, {'bskid': (int, 0), 'recid': (int, 0), 'note_title': (str, ""), 'note_body': (str, ""), 'editor_type': (str, ""), 'of': (str, ''), 'ln': (str, CFG_SITE_LANG), 'reply_to': (str, 0)}) _ = gettext_set_language(argd['ln']) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../yourbaskets/save_public_note", navmenuid = 'yourbaskets') if isGuestUser(uid): if not CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : "%s/yourbaskets/save_public_note%s" % ( CFG_SITE_URL, make_canonical_urlargd(argd, {})), "ln" : argd['ln']}, {}))) user_info = collect_user_info(req) if not user_info['precached_usebaskets']: return page_not_authorized(req, "../", \ text = _("You are not authorized to use baskets.")) (body, warnings, navtrail) = perform_request_save_public_note(uid=uid, bskid=argd['bskid'], recid=argd['recid'], note_title=argd['note_title'], note_body=argd['note_body'], editor_type=argd['editor_type'], ln=argd['ln'], reply_to=argd['reply_to']) # TODO: do not stat event if save was not succussful # register event in webstat basket_str = "%s (%d)" % (get_basket_name(argd['bskid']), argd['bskid']) if user_info['email']: user_str = "%s (%d)" % (user_info['email'], user_info['uid']) else: user_str = "" try: register_customevent("baskets", ["save_public_note", basket_str, user_str]) except: register_exception(suffix="Do the webstat tables exists? Try with 'webstatadmin --load-config'") return page(title = _("Display item and notes"), body = body, navtrail = navtrail, uid = uid, lastupdated = __lastupdated__, language = argd['ln'], warnings = warnings, req = req, navmenuid = 'yourbaskets', of = argd['of'], navtrail_append_title_p = 0) diff --git a/modules/webcomment/lib/webcomment_webinterface.py b/modules/webcomment/lib/webcomment_webinterface.py index 450ebf0c1..d99d8b782 100644 --- a/modules/webcomment/lib/webcomment_webinterface.py +++ b/modules/webcomment/lib/webcomment_webinterface.py @@ -1,818 +1,818 @@ # -*- coding: utf-8 -*- ## Comments and reviews for records. ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ Comments and reviews for records: web interface """ __lastupdated__ = """$Date$""" __revision__ = """$Id$""" import cgi from invenio.webcomment import check_recID_is_in_range, \ perform_request_display_comments_or_remarks, \ perform_request_add_comment_or_remark, \ perform_request_vote, \ perform_request_report, \ subscribe_user_to_discussion, \ unsubscribe_user_from_discussion, \ get_user_subscription_to_discussion, \ check_user_can_attach_file_to_comments, \ check_user_can_view_comments, \ check_user_can_send_comments, \ check_user_can_view_comment, \ query_get_comment from invenio.config import \ CFG_TMPDIR, \ CFG_SITE_LANG, \ CFG_SITE_URL, \ CFG_PREFIX, \ CFG_WEBCOMMENT_ALLOW_COMMENTS,\ CFG_WEBCOMMENT_ALLOW_REVIEWS, \ CFG_WEBCOMMENT_USE_JSMATH_IN_COMMENTS from invenio.webuser import getUid, page_not_authorized, isGuestUser, collect_user_info from invenio.webpage import page, pageheaderonly, pagefooteronly from invenio.search_engine import create_navtrail_links, \ guess_primary_collection_of_a_record, \ get_colID from invenio.urlutils import redirect_to_url, \ make_canonical_urlargd from invenio.errorlib import register_exception from invenio.messages import gettext_set_language from invenio.webinterface_handler import wash_urlargd, WebInterfaceDirectory from invenio.websearchadminlib import get_detailed_page_tabs from invenio.access_control_config import VIEWRESTRCOLL from invenio.access_control_mailcookie import \ mail_cookie_create_authorize_action, \ mail_cookie_create_common, \ mail_cookie_check_common, \ InvenioWebAccessMailCookieDeletedError, \ InvenioWebAccessMailCookieError from invenio.webcomment_config import \ CFG_WEBCOMMENT_MAX_ATTACHMENT_SIZE, \ CFG_WEBCOMMENT_MAX_ATTACHED_FILES import invenio.template webstyle_templates = invenio.template.load('webstyle') websearch_templates = invenio.template.load('websearch') try: from invenio.fckeditor_invenio_connector import FCKeditorConnectorInvenio fckeditor_available = True except ImportError, e: fckeditor_available = False import os from invenio import webinterface_handler_config as apache from invenio.bibdocfile import \ stream_file, \ decompose_file, \ propose_next_docname class WebInterfaceCommentsPages(WebInterfaceDirectory): """Defines the set of /comments pages.""" _exports = ['', 'display', 'add', 'vote', 'report', 'index', 'attachments', 'subscribe', 'unsubscribe'] def __init__(self, recid=-1, reviews=0): self.recid = recid self.discussion = reviews # 0:comments, 1:reviews self.attachments = WebInterfaceCommentsFiles(recid, reviews) def index(self, req, form): """ Redirects to display function """ return self.display(req, form) def display(self, req, form): """ Display comments (reviews if enabled) associated with record having id recid where recid>0. This function can also be used to display remarks associated with basket having id recid where recid<-99. @param ln: language @param recid: record id, integer @param do: display order hh = highest helpful score, review only lh = lowest helpful score, review only hs = highest star score, review only ls = lowest star score, review only od = oldest date nd = newest date @param ds: display since all= no filtering by date nd = n days ago nw = n weeks ago nm = n months ago ny = n years ago where n is a single digit integer between 0 and 9 @param nb: number of results per page @param p: results page @param voted: boolean, active if user voted for a review, see vote function @param reported: int, active if user reported a certain comment/review, see report function @param reviews: boolean, enabled for reviews, disabled for comments @param subscribed: int, 1 if user just subscribed to discussion, -1 if unsubscribed @return the full html page. """ argd = wash_urlargd(form, {'do': (str, "od"), 'ds': (str, "all"), 'nb': (int, 100), 'p': (int, 1), 'voted': (int, -1), 'reported': (int, -1), 'subscribed': (int, 0), 'cmtgrp': (list, ["latest"]) # 'latest' is now a reserved group/round name }) _ = gettext_set_language(argd['ln']) uid = getUid(req) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_comments(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) can_send_comments = False (auth_code, auth_msg) = check_user_can_send_comments(user_info, self.recid) if not auth_code: can_send_comments = True can_attach_files = False (auth_code, auth_msg) = check_user_can_attach_file_to_comments(user_info, self.recid) - if not auth_code and (user_info['email'] != 'guest' or user_info['apache_user']): + if not auth_code and (user_info['email'] != 'guest'): can_attach_files = True subscription = get_user_subscription_to_discussion(self.recid, uid) if subscription == 1: user_is_subscribed_to_discussion = True user_can_unsubscribe_from_discussion = True elif subscription == 2: user_is_subscribed_to_discussion = True user_can_unsubscribe_from_discussion = False else: user_is_subscribed_to_discussion = False user_can_unsubscribe_from_discussion = False #display_comment_rounds = [cmtgrp for cmtgrp in argd['cmtgrp'] if cmtgrp.isdigit() or cmtgrp == "all" or cmtgrp == "-1"] display_comment_rounds = argd['cmtgrp'] check_warnings = [] (ok, problem) = check_recID_is_in_range(self.recid, check_warnings, argd['ln']) if ok: (body, errors, warnings) = perform_request_display_comments_or_remarks(req=req, recID=self.recid, display_order=argd['do'], display_since=argd['ds'], nb_per_page=argd['nb'], page=argd['p'], ln=argd['ln'], voted=argd['voted'], reported=argd['reported'], subscribed=argd['subscribed'], reviews=self.discussion, uid=uid, can_send_comments=can_send_comments, can_attach_files=can_attach_files, user_is_subscribed_to_discussion=user_is_subscribed_to_discussion, user_can_unsubscribe_from_discussion=user_can_unsubscribe_from_discussion, display_comment_rounds=display_comment_rounds ) unordered_tabs = get_detailed_page_tabs(get_colID(guess_primary_collection_of_a_record(self.recid)), self.recid, ln=argd['ln']) ordered_tabs_id = [(tab_id, values['order']) for (tab_id, values) in unordered_tabs.iteritems()] ordered_tabs_id.sort(lambda x, y: cmp(x[1], y[1])) link_ln = '' if argd['ln'] != CFG_SITE_LANG: link_ln = '?ln=%s' % argd['ln'] tabs = [(unordered_tabs[tab_id]['label'], \ '%s/record/%s/%s%s' % (CFG_SITE_URL, self.recid, tab_id, link_ln), \ tab_id in ['comments', 'reviews'], unordered_tabs[tab_id]['enabled']) \ for (tab_id, order) in ordered_tabs_id if unordered_tabs[tab_id]['visible'] == True] top = webstyle_templates.detailed_record_container_top(self.recid, tabs, argd['ln']) bottom = webstyle_templates.detailed_record_container_bottom(self.recid, tabs, argd['ln']) title, description, keywords = websearch_templates.tmpl_record_page_header_content(req, self.recid, argd['ln']) navtrail = create_navtrail_links(cc=guess_primary_collection_of_a_record(self.recid), ln=argd['ln']) if navtrail: navtrail += ' > ' navtrail += ''% (CFG_SITE_URL, self.recid, argd['ln']) navtrail += title navtrail += '' navtrail += ' > %s' % (self.discussion==1 and _("Reviews") or _("Comments")) jsmathheader = '' if CFG_WEBCOMMENT_USE_JSMATH_IN_COMMENTS: jsmathheader = """ """ jqueryheader = ''' ''' % {'CFG_SITE_URL': CFG_SITE_URL} return pageheaderonly(title=title, navtrail=navtrail, uid=uid, verbose=1, metaheaderadd = jsmathheader + jqueryheader, req=req, language=argd['ln'], navmenuid='search', navtrail_append_title_p=0) + \ websearch_templates.tmpl_search_pagestart(argd['ln']) + \ top + body + bottom + \ websearch_templates.tmpl_search_pageend(argd['ln']) + \ pagefooteronly(lastupdated=__lastupdated__, language=argd['ln'], req=req) else: return page(title=_("Record Not Found"), body=problem, uid=uid, verbose=1, req=req, language=argd['ln'], warnings=check_warnings, errors=[], navmenuid='search') # Return the same page wether we ask for /record/123 or /record/123/ __call__ = index def add(self, req, form): """ Add a comment (review) to record with id recid where recid>0 Also works for adding a remark to basket with id recid where recid<-99 @param ln: languange @param recid: record id @param action: 'DISPLAY' to display add form 'SUBMIT' to submit comment once form is filled 'REPLY' to reply to an already existing comment @param msg: the body of the comment/review or remark @param score: star score of the review @param note: title of the review @param comid: comment id, needed for replying @param editor_type: the type of editor used for submitting the comment: 'textarea', 'fckeditor'. @param subscribe: if set, subscribe user to receive email notifications when new comment are added to this discussion @return the full html page. """ argd = wash_urlargd(form, {'action': (str, "DISPLAY"), 'msg': (str, ""), 'note': (str, ''), 'score': (int, 0), 'comid': (int, -1), 'editor_type': (str, ""), 'subscribe': (str, ""), 'cookie': (str, "") }) _ = gettext_set_language(argd['ln']) actions = ['DISPLAY', 'REPLY', 'SUBMIT'] uid = getUid(req) # Is site ready to accept comments? if uid == -1 or (not CFG_WEBCOMMENT_ALLOW_COMMENTS and not CFG_WEBCOMMENT_ALLOW_REVIEWS): return page_not_authorized(req, "../comments/add", navmenuid='search') # Is user allowed to post comment? user_info = collect_user_info(req) (auth_code_1, auth_msg_1) = check_user_can_view_comments(user_info, self.recid) (auth_code_2, auth_msg_2) = check_user_can_send_comments(user_info, self.recid) if isGuestUser(uid): cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) # Save user's value in cookie, so that these "POST" # parameters are not lost during login process msg_cookie = mail_cookie_create_common('comment_msg', {'msg': argd['msg'], 'note': argd['note'], 'score': argd['score'], 'editor_type': argd['editor_type'], 'subscribe': argd['subscribe']}, onetime=True) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri'] + '&cookie=' + msg_cookie}, {}) return redirect_to_url(req, target, norobot=True) elif (auth_code_1 or auth_code_2): return page_not_authorized(req, "../", \ text = auth_msg_1 + auth_msg_2) user_info = collect_user_info(req) can_attach_files = False (auth_code, auth_msg) = check_user_can_attach_file_to_comments(user_info, self.recid) - if not auth_code and (user_info['email'] != 'guest' or user_info['apache_user']): + if not auth_code and (user_info['email'] != 'guest'): can_attach_files = True warning_msgs = [] added_files = {} if can_attach_files: # User is allowed to attach files. Process the files file_too_big = False formfields = form.get('commentattachment[]', []) if not hasattr(formfields, "__getitem__"): # A single file was uploaded formfields = [formfields] for formfield in formfields[:CFG_WEBCOMMENT_MAX_ATTACHED_FILES]: if hasattr(formfield, "filename") and formfield.filename: filename = formfield.filename dir_to_open = os.path.join(CFG_TMPDIR, 'webcomment', str(uid)) try: assert(dir_to_open.startswith(CFG_TMPDIR)) except AssertionError: register_exception(req=req, prefix='User #%s tried to upload file to forbidden location: %s' \ % (uid, dir_to_open)) if not os.path.exists(dir_to_open): try: os.makedirs(dir_to_open) except: register_exception(req=req, alert_admin=True) ## Before saving the file to disc, wash the filename (in particular ## washing away UNIX and Windows (e.g. DFS) paths): filename = os.path.basename(filename.split('\\')[-1]) filename = filename.strip() if filename != "": # Check that file does not already exist n = 1 while os.path.exists(os.path.join(dir_to_open, filename)): basedir, name, extension = decompose_file(filename) new_name = propose_next_docname(name) filename = new_name + extension fp = open(os.path.join(dir_to_open, filename), "w") # FIXME: temporary, waiting for wsgi handler to be # fixed. Once done, read chunk by chunk ## while formfield.file: ## fp.write(formfield.file.read(10240)) fp.write(formfield.file.read()) fp.close() # Isn't this file too big? file_size = os.path.getsize(os.path.join(dir_to_open, filename)) if CFG_WEBCOMMENT_MAX_ATTACHMENT_SIZE > 0 and \ file_size > CFG_WEBCOMMENT_MAX_ATTACHMENT_SIZE: os.remove(os.path.join(dir_to_open, filename)) # One file is too big: record that, # dismiss all uploaded files and re-ask to # upload again file_too_big = True warning_msgs.append(('WRN_WEBCOMMENT_MAX_FILE_SIZE_REACHED', cgi.escape(filename), str(file_size/1024) + 'KB', str(CFG_WEBCOMMENT_MAX_ATTACHMENT_SIZE/1024) + 'KB')) else: added_files[filename] = os.path.join(dir_to_open, filename) if file_too_big: # One file was too big. Removed all uploaded filed for filepath in added_files.items(): try: os.remove(filepath) except: # File was already removed or does not exist? pass client_ip_address = req.remote_ip check_warnings = [] (ok, problem) = check_recID_is_in_range(self.recid, check_warnings, argd['ln']) if ok: title, description, keywords = websearch_templates.tmpl_record_page_header_content(req, self.recid, argd['ln']) navtrail = create_navtrail_links(cc=guess_primary_collection_of_a_record(self.recid)) if navtrail: navtrail += ' > ' navtrail += ''% (CFG_SITE_URL, self.recid, argd['ln']) navtrail += title navtrail += '' navtrail += '> %s' % (CFG_SITE_URL, self.recid, self.discussion==1 and 'reviews' or 'comments', argd['ln'], self.discussion==1 and _('Reviews') or _('Comments')) if argd['action'] not in actions: argd['action'] = 'DISPLAY' if not argd['msg']: # User had to login in-between, so retrieve msg # from cookie try: (kind, cookie_argd) = mail_cookie_check_common(argd['cookie'], delete=True) argd.update(cookie_argd) except InvenioWebAccessMailCookieDeletedError, e: return redirect_to_url(req, CFG_SITE_URL + '/record/' + \ str(self.recid) + (self.discussion==1 and \ '/reviews' or '/comments')) except InvenioWebAccessMailCookieError, e: # Invalid or empty cookie: continue pass subscribe = False if argd['subscribe'] and \ get_user_subscription_to_discussion(self.recid, uid) == 0: # User is not already subscribed, and asked to subscribe subscribe = True (body, errors, warnings) = perform_request_add_comment_or_remark(recID=self.recid, ln=argd['ln'], uid=uid, action=argd['action'], msg=argd['msg'], note=argd['note'], score=argd['score'], reviews=self.discussion, comID=argd['comid'], client_ip_address=client_ip_address, editor_type=argd['editor_type'], can_attach_files=can_attach_files, subscribe=subscribe, req=req, attached_files=added_files, warnings=warning_msgs) if self.discussion: title = _("Add Review") else: title = _("Add Comment") jqueryheader = ''' ''' % {'CFG_SITE_URL': CFG_SITE_URL} return page(title=title, body=body, navtrail=navtrail, uid=uid, language=CFG_SITE_LANG, verbose=1, errors=errors, warnings=warnings, req=req, navmenuid='search', metaheaderadd=jqueryheader) # id not in range else: return page(title=_("Record Not Found"), body=problem, uid=uid, verbose=1, req=req, warnings=check_warnings, errors=[], navmenuid='search') def vote(self, req, form): """ Vote positively or negatively for a comment/review. @param comid: comment/review id @param com_value: +1 to vote positively -1 to vote negatively @param recid: the id of the record the comment/review is associated with @param ln: language @param do: display order hh = highest helpful score, review only lh = lowest helpful score, review only hs = highest star score, review only ls = lowest star score, review only od = oldest date nd = newest date @param ds: display since all= no filtering by date nd = n days ago nw = n weeks ago nm = n months ago ny = n years ago where n is a single digit integer between 0 and 9 @param nb: number of results per page @param p: results page @param referer: http address of the calling function to redirect to (refresh) @param reviews: boolean, enabled for reviews, disabled for comments """ argd = wash_urlargd(form, {'comid': (int, -1), 'com_value': (int, 0), 'recid': (int, -1), 'do': (str, "od"), 'ds': (str, "all"), 'nb': (int, 100), 'p': (int, 1), 'referer': (str, None) }) client_ip_address = req.remote_ip uid = getUid(req) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_comments(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) success = perform_request_vote(argd['comid'], client_ip_address, argd['com_value'], uid) if argd['referer']: argd['referer'] += "?ln=%s&do=%s&ds=%s&nb=%s&p=%s&voted=%s&" % ( argd['ln'], argd['do'], argd['ds'], argd['nb'], argd['p'], success) redirect_to_url(req, argd['referer']) else: #Note: sent to comments display referer = "%s/record/%s/%s?&ln=%s&voted=1" referer %= (CFG_SITE_URL, self.recid, self.discussion == 1 and 'reviews' or 'comments', argd['ln']) redirect_to_url(req, referer) def report(self, req, form): """ Report a comment/review for inappropriate content @param comid: comment/review id @param recid: the id of the record the comment/review is associated with @param ln: language @param do: display order hh = highest helpful score, review only lh = lowest helpful score, review only hs = highest star score, review only ls = lowest star score, review only od = oldest date nd = newest date @param ds: display since all= no filtering by date nd = n days ago nw = n weeks ago nm = n months ago ny = n years ago where n is a single digit integer between 0 and 9 @param nb: number of results per page @param p: results page @param referer: http address of the calling function to redirect to (refresh) @param reviews: boolean, enabled for reviews, disabled for comments """ argd = wash_urlargd(form, {'comid': (int, -1), 'recid': (int, -1), 'do': (str, "od"), 'ds': (str, "all"), 'nb': (int, 100), 'p': (int, 1), 'referer': (str, None) }) client_ip_address = req.remote_ip uid = getUid(req) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_comments(user_info, self.recid) - if (auth_code and not user_info['apache_user']) or user_info['email'] == 'guest': + if auth_code or user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) success = perform_request_report(argd['comid'], client_ip_address, uid) if argd['referer']: argd['referer'] += "?ln=%s&do=%s&ds=%s&nb=%s&p=%s&reported=%s&" % (argd['ln'], argd['do'], argd['ds'], argd['nb'], argd['p'], str(success)) redirect_to_url(req, argd['referer']) else: #Note: sent to comments display referer = "%s/record/%s/%s/display?ln=%s&voted=1" referer %= (CFG_SITE_URL, self.recid, self.discussion==1 and 'reviews' or 'comments', argd['ln']) redirect_to_url(req, referer) def subscribe(self, req, form): """ Subscribe current user to receive email notification when new comments are added to current discussion. """ argd = wash_urlargd(form, {'referer': (str, None)}) uid = getUid(req) user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_comments(user_info, self.recid) if isGuestUser(uid): cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) success = subscribe_user_to_discussion(self.recid, uid) display_url = "%s/record/%s/comments/display?subscribed=%s&ln=%s" % \ (CFG_SITE_URL, self.recid, str(success), argd['ln']) redirect_to_url(req, display_url) def unsubscribe(self, req, form): """ Unsubscribe current user from current discussion. """ argd = wash_urlargd(form, {'referer': (str, None)}) user_info = collect_user_info(req) uid = getUid(req) if isGuestUser(uid): cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) success = unsubscribe_user_from_discussion(self.recid, uid) display_url = "%s/record/%s/comments/display?subscribed=%s&ln=%s" % \ (CFG_SITE_URL, self.recid, str(-success), argd['ln']) redirect_to_url(req, display_url) class WebInterfaceCommentsFiles(WebInterfaceDirectory): """Handle upload and access to files for comments. The upload is currently only available through the FCKeditor. """ #_exports = ['put'] # 'get' is handled by _lookup(..) def __init__(self, recid=-1, reviews=0): self.recid = recid self.discussion = reviews # 0:comments, 1:reviews def _lookup(self, component, path): """ This handler is invoked for the dynamic URLs (for getting and putting attachments) Eg: CFG_SITE_URL/record/5953/comments/attachments/get/652/myfile.pdf """ if component == 'get' and len(path) > 1: comid = path[0] # comment ID file_name = '/'.join(path[1:]) # the filename def answer_get(req, form): """Accessing files attached to comments.""" form['file'] = file_name form['comid'] = comid return self._get(req, form) return answer_get, [] # All other cases: file not found return None, [] def _get(self, req, form): """ Returns a file attached to a comment. Example: CFG_SITE_URL/record/5953/comments/attachments/get/652/myfile.pdf where 652 is the comment ID """ argd = wash_urlargd(form, {'file': (str, None), 'comid': (int, 0)}) _ = gettext_set_language(argd['ln']) # Can user view this record, i.e. can user access its # attachments? uid = getUid(req) user_info = collect_user_info(req) # Check that user can view record, and its comments (protected # with action "viewcomment") (auth_code, auth_msg) = check_user_can_view_comments(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg) # Does comment exist? if not query_get_comment(argd['comid']): req.status = apache.HTTP_NOT_FOUND return page(title=_("Page Not Found"), body=_('The requested comment could not be found'), req=req) # Check that user can view this particular comment, protected # using its own restriction (auth_code, auth_msg) = check_user_can_view_comment(user_info, argd['comid']) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg, ln=argd['ln']) if not argd['file'] is None: # Prepare path to file on disk. Normalize the path so that # ../ and other dangerous components are removed. path = os.path.abspath(CFG_PREFIX + '/var/data/comments/' + \ str(self.recid) + '/' + str(argd['comid']) + \ '/' + argd['file']) # Check that we are really accessing attachements # directory, for the declared record. if path.startswith(CFG_PREFIX + '/var/data/comments/' + \ str(self.recid)) and \ os.path.exists(path): return stream_file(req, path) # Send error 404 in all other cases req.status = apache.HTTP_NOT_FOUND return page(title=_("Page Not Found"), body=_('The requested file could not be found'), req=req, language=argd['ln']) ## def put(self, req, form): ## """ ## Process requests received from FCKeditor to upload files, etc. ## """ ## if not fckeditor_available: ## return ## uid = getUid(req) ## # URL where the file can be fetched after upload ## user_files_path = '%(CFG_SITE_URL)s/record/%(recid)i/comments/attachments/get/%(uid)s' % \ ## {'uid': uid, ## 'recid': self.recid, ## 'CFG_SITE_URL': CFG_SITE_URL} ## # Path to directory where uploaded files are saved ## user_files_absolute_path = '%(CFG_PREFIX)s/var/data/comments/%(recid)s/%(uid)s' % \ ## {'uid': uid, ## 'recid': self.recid, ## 'CFG_PREFIX': CFG_PREFIX} ## # Create a Connector instance to handle the request ## conn = FCKeditorConnectorInvenio(form, recid=self.recid, uid=uid, ## allowed_commands=['QuickUpload'], ## allowed_types = ['File', 'Image', 'Flash', 'Media'], ## user_files_path = user_files_path, ## user_files_absolute_path = user_files_absolute_path) ## # Check that user can upload attachments for comments. ## user_info = collect_user_info(req) ## (auth_code, auth_msg) = check_user_can_attach_file_to_comments(user_info, self.recid) -## if user_info['email'] == 'guest' and not user_info['apache_user']: +## if user_info['email'] == 'guest': ## # User is guest: must login prior to upload ## data = conn.sendUploadResults(1, '', '', 'Please login before uploading file.') ## elif auth_code: ## # User cannot submit ## data = conn.sendUploadResults(1, '', '', 'Sorry, you are not allowed to submit files.') ## else: ## # Process the upload and get the response ## data = conn.doResponse() ## # Transform the headers into something ok for mod_python ## for header in conn.headers: ## if not header is None: ## if header[0] == 'Content-Type': ## req.content_type = header[1] ## else: ## req.headers_out[header[0]] = header[1] ## # Send our response ## req.send_http_header() ## req.write(data) diff --git a/modules/websearch/lib/websearch_webinterface.py b/modules/websearch/lib/websearch_webinterface.py index db4988672..9af7a00b4 100644 --- a/modules/websearch/lib/websearch_webinterface.py +++ b/modules/websearch/lib/websearch_webinterface.py @@ -1,1289 +1,1279 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """WebSearch URL handler.""" __revision__ = "$Id$" import cgi import os import datetime import time import sys from urllib import quote from invenio import webinterface_handler_config as apache #maximum number of collaborating authors etc shown in GUI MAX_COLLAB_LIST = 10 MAX_KEYWORD_LIST = 10 MAX_VENUE_LIST = 10 #tag constants AUTHOR_TAG = "100__a" AUTHOR_INST_TAG = "100__u" COAUTHOR_TAG = "700__a" COAUTHOR_INST_TAG = "700__u" VENUE_TAG = "909C4p" KEYWORD_TAG = "695__a" FKEYWORD_TAG = "6531_a" CFG_INSPIRE_UNWANTED_KEYWORDS_START= ['talk', 'conference', 'conference proceedings', 'numerical calculations', 'experimental results', 'review', 'bibliography', 'upper limit', 'lower limit', 'tables', 'search for', 'on-shell', 'off-shell', 'formula', 'lectures', 'book', 'thesis'] CFG_INSPIRE_UNWANTED_KEYWORDS_MIDDLE = ['GeV', '(('] if sys.hexversion < 0x2040000: # pylint: disable=W0622 from sets import Set as set # pylint: enable=W0622 from invenio.config import \ CFG_SITE_URL, \ CFG_SITE_NAME, \ CFG_CACHEDIR, \ CFG_SITE_LANG, \ CFG_SITE_SECURE_URL, \ CFG_BIBRANK_SHOW_DOWNLOAD_STATS, \ CFG_WEBSEARCH_INSTANT_BROWSE_RSS, \ CFG_WEBSEARCH_RSS_TTL, \ CFG_WEBSEARCH_RSS_MAX_CACHED_REQUESTS, \ CFG_WEBSEARCH_DEFAULT_SEARCH_INTERFACE, \ CFG_WEBDIR, \ CFG_WEBSEARCH_USE_JSMATH_FOR_FORMATS, \ CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS, \ CFG_WEBSEARCH_PERMITTED_RESTRICTED_COLLECTIONS_LEVEL, \ CFG_WEBSEARCH_USE_ALEPH_SYSNOS, \ CFG_WEBSEARCH_RSS_I18N_COLLECTIONS, \ CFG_INSPIRE_SITE from invenio.dbquery import Error from invenio.webinterface_handler import wash_urlargd, WebInterfaceDirectory from invenio.urlutils import redirect_to_url, make_canonical_urlargd, drop_default_urlargd from invenio.webuser import getUid, page_not_authorized, get_user_preferences, \ - collect_user_info, http_check_credentials, logoutUser, isUserSuperAdmin + collect_user_info, logoutUser, isUserSuperAdmin from invenio.websubmit_webinterface import WebInterfaceFilesPages from invenio.webcomment_webinterface import WebInterfaceCommentsPages from invenio.bibcirculation_webinterface import WebInterfaceHoldingsPages from invenio.webpage import page, create_error_box from invenio.messages import gettext_set_language from invenio.search_engine import check_user_can_view_record, \ collection_reclist_cache, \ collection_restricted_p, \ create_similarly_named_authors_link_box, \ get_colID, \ get_coll_i18nname, \ get_fieldvalues, \ get_fieldvalues_alephseq_like, \ get_most_popular_field_values, \ get_mysql_recid_from_aleph_sysno, \ guess_primary_collection_of_a_record, \ page_end, \ page_start, \ perform_request_cache, \ perform_request_log, \ perform_request_search, \ restricted_collection_cache from invenio.access_control_engine import acc_authorize_action from invenio.access_control_config import VIEWRESTRCOLL from invenio.access_control_mailcookie import mail_cookie_create_authorize_action from invenio.bibformat import format_records from invenio.bibformat_engine import get_output_formats from invenio.websearch_webcoll import mymkdir, get_collection from invenio.intbitset import intbitset from invenio.bibupload import find_record_from_sysno from invenio.bibrank_citation_searcher import get_cited_by_list from invenio.bibrank_downloads_indexer import get_download_weight_total from invenio.search_engine_summarizer import summarize_records from invenio.errorlib import register_exception from invenio.bibedit_webinterface import WebInterfaceEditPages from invenio.bibeditmulti_webinterface import WebInterfaceMultiEditPages from invenio.bibmerge_webinterface import WebInterfaceMergePages import invenio.template websearch_templates = invenio.template.load('websearch') search_results_default_urlargd = websearch_templates.search_results_default_urlargd search_interface_default_urlargd = websearch_templates.search_interface_default_urlargd try: output_formats = [output_format['attrs']['code'].lower() for output_format in \ get_output_formats(with_attributes=True).values()] except KeyError: output_formats = ['xd', 'xm', 'hd', 'hb', 'hs', 'hx'] output_formats.extend(['hm', 't', 'h']) def wash_search_urlargd(form): """ Create canonical search arguments from those passed via web form. """ argd = wash_urlargd(form, search_results_default_urlargd) if argd.has_key('as'): argd['aas'] = argd['as'] del argd['as'] # Sometimes, users pass ot=245,700 instead of # ot=245&ot=700. Normalize that. ots = [] for ot in argd['ot']: ots += ot.split(',') argd['ot'] = ots # We can either get the mode of function as # action=, or by setting action_browse or # action_search. if argd['action_browse']: argd['action'] = 'browse' elif argd['action_search']: argd['action'] = 'search' else: if argd['action'] not in ('browse', 'search'): argd['action'] = 'search' del argd['action_browse'] del argd['action_search'] return argd class WebInterfaceUnAPIPages(WebInterfaceDirectory): """ Handle /unapi set of pages.""" _exports = [''] def __call__(self, req, form): argd = wash_urlargd(form, { 'id' : (int, 0), 'format' : (str, '')}) formats_dict = get_output_formats(True) formats = {} for format in formats_dict.values(): if format['attrs']['visibility']: formats[format['attrs']['code'].lower()] = format['attrs']['content_type'] del formats_dict if argd['id'] and argd['format']: ## Translate back common format names format = { 'nlm' : 'xn', 'marcxml' : 'xm', 'dc' : 'xd', 'endnote' : 'xe', 'mods' : 'xo' }.get(argd['format'], argd['format']) if format in formats: redirect_to_url(req, '%s/record/%s/export/%s' % (CFG_SITE_URL, argd['id'], format)) else: raise apache.SERVER_RETURN, apache.HTTP_NOT_ACCEPTABLE elif argd['id']: return websearch_templates.tmpl_unapi(formats, identifier=argd['id']) else: return websearch_templates.tmpl_unapi(formats) index = __call__ class WebInterfaceAuthorPages(WebInterfaceDirectory): """ Handle /author/Doe%2C+John etc set of pages.""" _exports = ['author'] def __init__(self, authorname=''): """Constructor.""" self.authorname = authorname def _lookup(self, component, path): """This handler parses dynamic URLs (/author/John+Doe).""" return WebInterfaceAuthorPages(component), path def __call__(self, req, form): """Serve the page in the given language.""" argd = wash_urlargd(form, {'ln': (str, CFG_SITE_LANG), 'verbose': (int, 0) }) ln = argd['ln'] verbose = argd['verbose'] req.argd = argd #needed since perform_req_search # start page req.content_type = "text/html" req.send_http_header() uid = getUid(req) page_start(req, "hb", "", "", ln, uid) #wants to check it in case of no results self.authorname = self.authorname.replace("+"," ") if not self.authorname: return websearch_templates.tmpl_author_information(req, {}, self.authorname, 0, {}, {}, {}, {}, {}, ln) #let's see what takes time.. time1 = time.time() genstart = time1 time2 = time.time() #search the publications by this author pubs = perform_request_search(req=req, p=self.authorname, f="exactauthor") #get most frequent authors of these pubs popular_author_tuples = get_most_popular_field_values(pubs, (AUTHOR_TAG, COAUTHOR_TAG)) authors = [] for (auth, frequency) in popular_author_tuples: if len(authors) < MAX_COLLAB_LIST: authors.append(auth) time1 = time.time() if verbose == 9: req.write("
    popularized authors: "+str(time1-time2)+"
    ") #and publication venues venuetuples = get_most_popular_field_values(pubs, (VENUE_TAG)) time2 = time.time() if verbose == 9: req.write("
    venues: "+str(time2-time1)+"
    ") #and keywords kwtuples = get_most_popular_field_values(pubs, (KEYWORD_TAG, FKEYWORD_TAG), count_repetitive_values=False) if CFG_INSPIRE_SITE: # filter kw tuples against unwanted keywords: kwtuples_filtered = () for (kw, num) in kwtuples: kwlower = kw.lower() kwlower_unwanted = False for unwanted_keyword in CFG_INSPIRE_UNWANTED_KEYWORDS_START: if kwlower.startswith(unwanted_keyword): kwlower_unwanted = True # unwanted keyword found break for unwanted_keyword in CFG_INSPIRE_UNWANTED_KEYWORDS_MIDDLE: if unwanted_keyword in kwlower: kwlower_unwanted = True # unwanted keyword found break if not kwlower_unwanted: kwtuples_filtered += ((kw, num),) kwtuples = kwtuples_filtered time1 = time.time() if verbose == 9: req.write("
    keywords: "+str(time1-time2)+"
    ") #construct a simple list of tuples that contains keywords that appear more than once #moreover, limit the length of the list to MAX_KEYWORD_LIST kwtuples = kwtuples[0:MAX_KEYWORD_LIST] vtuples = venuetuples[0:MAX_VENUE_LIST] #remove the author in question from authors: they are associates if (authors.count(self.authorname) > 0): authors.remove(self.authorname) authors = authors[0:MAX_COLLAB_LIST] #cut extra time2 = time.time() if verbose == 9: req.write("
    misc: "+str(time2-time1)+"
    ") #a dict. keys: affiliations, values: lists of publications author_aff_pubs = self.get_institute_pub_dict(pubs) time1 = time.time() if verbose == 9: req.write("
    affiliations: "+str(time1-time2)+"
    ") totaldownloads = 0 if CFG_BIBRANK_SHOW_DOWNLOAD_STATS: #find out how many times these records have been downloaded recsloads = {} recsloads = get_download_weight_total(recsloads, pubs) #sum up for k in recsloads.keys(): totaldownloads = totaldownloads + recsloads[k] #get cited by.. citedbylist = get_cited_by_list(pubs) time1 = time.time() if verbose == 9: req.write("
    citedby: "+str(time1-time2)+"
    ") #finally all stuff there, call the template websearch_templates.tmpl_author_information(req, pubs, self.authorname, totaldownloads, author_aff_pubs, citedbylist, kwtuples, authors, vtuples, ln) time1 = time.time() #cited-by summary out = summarize_records(intbitset(pubs), 'hcs', ln, self.authorname, 'exactauthor', req) time2 = time.time() if verbose == 9: req.write("
    summarizer: "+str(time2-time1)+"
    ") req.write(out) simauthbox = create_similarly_named_authors_link_box(self.authorname) req.write(simauthbox) if verbose == 9: req.write("
    all: "+str(time.time()-genstart)+"
    ") return page_end(req, 'hb', ln) def get_institute_pub_dict(self, recids): """return a dictionary consisting of institute -> list of publications""" author_aff_pubs = {} #the dictionary to be built for recid in recids: #iterate all so that we get first author's intitute #if this the first author OR #"his" institute if he is an affliate author affus = [] #list of insts from the given record mainauthors = get_fieldvalues(recid, AUTHOR_TAG) mainauthor = " " if mainauthors: mainauthor = mainauthors[0] if (mainauthor == self.authorname): affus = get_fieldvalues(recid, AUTHOR_INST_TAG) else: #search for coauthors.. coauthor_field_lines = [] coauthorfield_content = get_fieldvalues_alephseq_like(recid, \ COAUTHOR_TAG[:3]) if coauthorfield_content: coauthor_field_lines = coauthorfield_content.split("\n") for line in coauthor_field_lines: if line.count(self.authorname) > 0: #get affilitions .. the correct ones are $$+code code = COAUTHOR_INST_TAG[-1] myparts = line.split("$$") for part in myparts: if part and part[0] == code: myaff = part[1:] affus.append(myaff) break #if this is empty, add a dummy " " value if (affus == []): affus = [" "] for a in affus: #add in author_aff_pubs if (author_aff_pubs.has_key(a)): tmp = author_aff_pubs[a] tmp.append(recid) author_aff_pubs[a] = tmp else: author_aff_pubs[a] = [recid] return author_aff_pubs index = __call__ class WebInterfaceRecordPages(WebInterfaceDirectory): """ Handling of a /record/ URL fragment """ _exports = ['', 'files', 'reviews', 'comments', 'usage', 'references', 'export', 'citations', 'holdings', 'edit', 'keywords', 'multiedit', 'merge', 'plots'] #_exports.extend(output_formats) def __init__(self, recid, tab, format=None): self.recid = recid self.tab = tab self.format = format self.files = WebInterfaceFilesPages(self.recid) self.reviews = WebInterfaceCommentsPages(self.recid, reviews=1) self.comments = WebInterfaceCommentsPages(self.recid) self.usage = self self.references = self self.keywords = self self.holdings = WebInterfaceHoldingsPages(self.recid) self.citations = self self.plots = self self.export = WebInterfaceRecordExport(self.recid, self.format) self.edit = WebInterfaceEditPages(self.recid) self.merge = WebInterfaceMergePages(self.recid) return def __call__(self, req, form): argd = wash_search_urlargd(form) argd['recid'] = self.recid argd['tab'] = self.tab if self.format is not None: argd['of'] = self.format req.argd = argd uid = getUid(req) if uid == -1: return page_not_authorized(req, "../", text="You are not authorized to view this record.", navmenuid='search') elif uid > 0: pref = get_user_preferences(uid) try: if not form.has_key('rg'): # fetch user rg preference only if not overridden via URL argd['rg'] = int(pref['websearch_group_records']) except (KeyError, ValueError): pass user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_record(user_info, self.recid) if argd['rg'] > CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS and not isUserSuperAdmin(user_info): argd['rg'] = CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = CFG_SITE_SECURE_URL + '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : CFG_SITE_URL + req.unparsed_uri}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg,\ navmenuid='search') # mod_python does not like to return [] in case when of=id: out = perform_request_search(req, **argd) if out == []: return str(out) else: return out # Return the same page wether we ask for /record/123 or /record/123/ index = __call__ class WebInterfaceRecordRestrictedPages(WebInterfaceDirectory): """ Handling of a /record-restricted/ URL fragment """ _exports = ['', 'files', 'reviews', 'comments', 'usage', 'references', 'export', 'citations', 'holdings', 'edit', 'keywords', 'multiedit', 'merge', 'plots'] #_exports.extend(output_formats) def __init__(self, recid, tab, format=None): self.recid = recid self.tab = tab self.format = format self.files = WebInterfaceFilesPages(self.recid) self.reviews = WebInterfaceCommentsPages(self.recid, reviews=1) self.comments = WebInterfaceCommentsPages(self.recid) self.usage = self self.references = self self.keywords = self self.holdings = WebInterfaceHoldingsPages(self.recid) self.citations = self self.plots = self self.export = WebInterfaceRecordExport(self.recid, self.format) self.edit = WebInterfaceEditPages(self.recid) self.merge = WebInterfaceMergePages(self.recid) return def __call__(self, req, form): argd = wash_search_urlargd(form) argd['recid'] = self.recid if self.format is not None: argd['of'] = self.format req.argd = argd uid = getUid(req) user_info = collect_user_info(req) if uid == -1: return page_not_authorized(req, "../", text="You are not authorized to view this record.", navmenuid='search') elif uid > 0: pref = get_user_preferences(uid) try: if not form.has_key('rg'): # fetch user rg preference only if not overridden via URL argd['rg'] = int(pref['websearch_group_records']) except (KeyError, ValueError): pass if argd['rg'] > CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS and not isUserSuperAdmin(user_info): argd['rg'] = CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS record_primary_collection = guess_primary_collection_of_a_record(self.recid) if collection_restricted_p(record_primary_collection): (auth_code, dummy) = acc_authorize_action(user_info, VIEWRESTRCOLL, collection=record_primary_collection) if auth_code: return page_not_authorized(req, "../", text="You are not authorized to view this record.", navmenuid='search') # Keep all the arguments, they might be reused in the # record page itself to derivate other queries req.argd = argd # mod_python does not like to return [] in case when of=id: out = perform_request_search(req, **argd) if out == []: return str(out) else: return out # Return the same page wether we ask for /record/123 or /record/123/ index = __call__ class WebInterfaceSearchResultsPages(WebInterfaceDirectory): """ Handling of the /search URL and its sub-pages. """ _exports = ['', 'authenticate', 'cache', 'log'] def __call__(self, req, form): """ Perform a search. """ argd = wash_search_urlargd(form) _ = gettext_set_language(argd['ln']) if req.method == 'POST': raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED uid = getUid(req) user_info = collect_user_info(req) if uid == -1: return page_not_authorized(req, "../", text = _("You are not authorized to view this area."), navmenuid='search') elif uid > 0: pref = get_user_preferences(uid) try: if not form.has_key('rg'): # fetch user rg preference only if not overridden via URL argd['rg'] = int(pref['websearch_group_records']) except (KeyError, ValueError): pass if CFG_WEBSEARCH_PERMITTED_RESTRICTED_COLLECTIONS_LEVEL == 2: ## Let's update the current collections list with all ## the restricted collections the user has rights to view. try: restricted_collections = user_info['precached_permitted_restricted_collections'] argd_collections = set(argd['c']) argd_collections.update(restricted_collections) argd['c'] = list(argd_collections) except KeyError: pass if argd['rg'] > CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS and not isUserSuperAdmin(user_info): argd['rg'] = CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS involved_collections = set() involved_collections.update(argd['c']) involved_collections.add(argd['cc']) if argd['id'] > 0: argd['recid'] = argd['id'] if argd['idb'] > 0: argd['recidb'] = argd['idb'] if argd['sysno']: tmp_recid = find_record_from_sysno(argd['sysno']) if tmp_recid: argd['recid'] = tmp_recid if argd['sysnb']: tmp_recid = find_record_from_sysno(argd['sysnb']) if tmp_recid: argd['recidb'] = tmp_recid if argd['recid'] > 0: if argd['recidb'] > argd['recid']: # Hack to check if among the restricted collections # at least a record of the range is there and # then if the user is not authorized for that # collection. recids = intbitset(xrange(argd['recid'], argd['recidb'])) restricted_collection_cache.recreate_cache_if_needed() for collname in restricted_collection_cache.cache: (auth_code, auth_msg) = acc_authorize_action(user_info, VIEWRESTRCOLL, collection=collname) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': coll_recids = get_collection(collname).reclist if coll_recids & recids: cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : collname}) target = CFG_SITE_SECURE_URL + '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : CFG_SITE_URL + req.unparsed_uri}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg,\ navmenuid='search') else: involved_collections.add(guess_primary_collection_of_a_record(argd['recid'])) # If any of the collection requires authentication, redirect # to the authentication form. for coll in involved_collections: if collection_restricted_p(coll): (auth_code, auth_msg) = acc_authorize_action(user_info, VIEWRESTRCOLL, collection=coll) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : coll}) target = CFG_SITE_SECURE_URL + '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : CFG_SITE_URL + req.unparsed_uri}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg,\ navmenuid='search') # Keep all the arguments, they might be reused in the # search_engine itself to derivate other queries req.argd = argd # mod_python does not like to return [] in case when of=id: out = perform_request_search(req, **argd) if out == []: return str(out) else: return out def cache(self, req, form): """Search cache page.""" argd = wash_urlargd(form, {'action': (str, 'show')}) return perform_request_cache(req, action=argd['action']) def log(self, req, form): """Search log page.""" argd = wash_urlargd(form, {'date': (str, '')}) return perform_request_log(req, date=argd['date']) def authenticate(self, req, form): """Restricted search results pages.""" argd = wash_search_urlargd(form) user_info = collect_user_info(req) for coll in argd['c'] + [argd['cc']]: if collection_restricted_p(coll): (auth_code, auth_msg) = acc_authorize_action(user_info, VIEWRESTRCOLL, collection=coll) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : coll}) target = CFG_SITE_SECURE_URL + '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : CFG_SITE_URL + req.unparsed_uri}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg,\ navmenuid='search') # Keep all the arguments, they might be reused in the # search_engine itself to derivate other queries req.argd = argd uid = getUid(req) if uid > 0: pref = get_user_preferences(uid) try: if not form.has_key('rg'): # fetch user rg preference only if not overridden via URL argd['rg'] = int(pref['websearch_group_records']) except (KeyError, ValueError): pass # mod_python does not like to return [] in case when of=id: out = perform_request_search(req, **argd) if out == []: return str(out) else: return out index = __call__ class WebInterfaceLegacySearchPages(WebInterfaceDirectory): """ Handling of the /search.py URL and its sub-pages. """ _exports = ['', ('authenticate', 'index')] def __call__(self, req, form): """ Perform a search. """ argd = wash_search_urlargd(form) # We either jump into the generic search form, or the specific # /record/... display if a recid is requested if argd['recid'] != -1: target = '/record/%d' % argd['recid'] del argd['recid'] else: target = '/search' target += make_canonical_urlargd(argd, search_results_default_urlargd) return redirect_to_url(req, target, apache.HTTP_MOVED_PERMANENTLY) index = __call__ # Parameters for the legacy URLs, of the form /?c=ALEPH legacy_collection_default_urlargd = { 'as': (int, CFG_WEBSEARCH_DEFAULT_SEARCH_INTERFACE), 'aas': (int, CFG_WEBSEARCH_DEFAULT_SEARCH_INTERFACE), 'verbose': (int, 0), 'c': (str, CFG_SITE_NAME)} class WebInterfaceSearchInterfacePages(WebInterfaceDirectory): """ Handling of collection navigation.""" _exports = [('index.py', 'legacy_collection'), ('', 'legacy_collection'), ('search.py', 'legacy_search'), 'search', 'openurl', 'opensearchdescription', 'logout_SSO_hook'] search = WebInterfaceSearchResultsPages() legacy_search = WebInterfaceLegacySearchPages() def logout_SSO_hook(self, req, form): """Script triggered by the display of the centralized SSO logout dialog. It logouts the user from Invenio and stream back the expected picture.""" logoutUser(req) req.content_type = 'image/gif' req.encoding = None req.filename = 'wsignout.gif' req.headers_out["Content-Disposition"] = "inline; filename=wsignout.gif" req.set_content_length(os.path.getsize('%s/img/wsignout.gif' % CFG_WEBDIR)) req.send_http_header() req.sendfile('%s/img/wsignout.gif' % CFG_WEBDIR) def _lookup(self, component, path): """ This handler is invoked for the dynamic URLs (for collections and records)""" if component == 'collection': c = '/'.join(path) def answer(req, form): """Accessing collections cached pages.""" # Accessing collections: this is for accessing the # cached page on top of each collection. argd = wash_urlargd(form, search_interface_default_urlargd) # We simply return the cached page of the collection argd['c'] = c if not argd['c']: # collection argument not present; display # home collection by default argd['c'] = CFG_SITE_NAME # Treat `as' argument specially: if argd.has_key('as'): argd['aas'] = argd['as'] del argd['as'] return display_collection(req, **argd) return answer, [] elif component == 'record' and path and path[0] == 'merge': return WebInterfaceMergePages(), path[1:] elif component == 'record' and path and path[0] == 'edit': return WebInterfaceEditPages(), path[1:] elif component == 'record' and path and path[0] == 'multiedit': return WebInterfaceMultiEditPages(), path[1:] elif component == 'record' or component == 'record-restricted': try: if CFG_WEBSEARCH_USE_ALEPH_SYSNOS: # let us try to recognize /record/ style of URLs: x = get_mysql_recid_from_aleph_sysno(path[0]) if x: recid = x else: recid = int(path[0]) else: recid = int(path[0]) except IndexError: # display record #1 for URL /record without a number recid = 1 except ValueError: if path[0] == '': # display record #1 for URL /record/ without a number recid = 1 else: # display page not found for URLs like /record/foo return None, [] if recid <= 0: # display page not found for URLs like /record/-5 or /record/0 return None, [] format = None tab = '' try: if path[1] in ['', 'files', 'reviews', 'comments', 'usage', 'references', 'citations', 'holdings', 'edit', 'keywords', 'multiedit', 'merge', 'plots']: tab = path[1] elif path[1] == 'export': tab = '' format = path[2] # format = None # elif path[1] in output_formats: # tab = '' # format = path[1] else: # display page not found for URLs like /record/references # for a collection where 'references' tabs is not visible return None, [] except IndexError: # Keep normal url if tabs is not specified pass #if component == 'record-restricted': #return WebInterfaceRecordRestrictedPages(recid, tab, format), path[1:] #else: return WebInterfaceRecordPages(recid, tab, format), path[1:] return None, [] def openurl(self, req, form): """ OpenURL Handler.""" argd = wash_urlargd(form, websearch_templates.tmpl_openurl_accepted_args) ret_url = websearch_templates.tmpl_openurl2invenio(argd) if ret_url: return redirect_to_url(req, ret_url) else: return redirect_to_url(req, CFG_SITE_URL) def opensearchdescription(self, req, form): """OpenSearch description file""" req.content_type = "application/opensearchdescription+xml" req.send_http_header() argd = wash_urlargd(form, {'ln': (str, CFG_SITE_LANG), 'verbose': (int, 0) }) return websearch_templates.tmpl_opensearch_description(ln=argd['ln']) def legacy_collection(self, req, form): """Collection URL backward compatibility handling.""" accepted_args = dict(legacy_collection_default_urlargd) - accepted_args.update({'referer' : (str, ''), - 'realm' : (str, '')}) argd = wash_urlargd(form, accepted_args) - # Apache authentication stuff - if argd['realm']: - http_check_credentials(req, argd['realm']) - return redirect_to_url(req, argd['referer'] or '%s/youraccount/youradminactivities' % CFG_SITE_SECURE_URL, norobot=True) - - del argd['referer'] - del argd['realm'] - # Treat `as' argument specially: if argd.has_key('as'): argd['aas'] = argd['as'] del argd['as'] # If we specify no collection, then we don't need to redirect # the user, so that accessing returns the # default collection. if not form.has_key('c'): return display_collection(req, **argd) # make the collection an element of the path, and keep the # other query elements as is. If the collection is CFG_SITE_NAME, # however, redirect to the main URL. c = argd['c'] del argd['c'] if c == CFG_SITE_NAME: target = '/' else: target = '/collection/' + quote(c) # Treat `as' argument specially: # We are going to redirect, so replace `aas' by `as' visible argument: if argd.has_key('aas'): argd['as'] = argd['aas'] del argd['aas'] target += make_canonical_urlargd(argd, legacy_collection_default_urlargd) return redirect_to_url(req, target) def display_collection(req, c, aas, verbose, ln): """Display search interface page for collection c by looking in the collection cache.""" _ = gettext_set_language(ln) req.argd = drop_default_urlargd({'aas': aas, 'verbose': verbose, 'ln': ln}, search_interface_default_urlargd) # get user ID: try: uid = getUid(req) user_preferences = {} if uid == -1: return page_not_authorized(req, "../", text="You are not authorized to view this collection", navmenuid='search') elif uid > 0: user_preferences = get_user_preferences(uid) except Error: register_exception(req=req, alert_admin=True) return page(title=_("Internal Error"), body = create_error_box(req, verbose=verbose, ln=ln), description="%s - Internal Error" % CFG_SITE_NAME, keywords="%s, Internal Error" % CFG_SITE_NAME, language=ln, req=req, navmenuid='search') # start display: req.content_type = "text/html" req.send_http_header() # deduce collection id: colID = get_colID(c) if type(colID) is not int: page_body = '

    ' + (_("Sorry, collection %s does not seem to exist.") % ('' + str(c) + '')) + '

    ' page_body = '

    ' + (_("You may want to start browsing from %s.") % ('' + get_coll_i18nname(CFG_SITE_NAME, ln) + '')) + '

    ' if req.header_only: raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND return page(title=_("Collection %s Not Found") % cgi.escape(c), body=page_body, description=(CFG_SITE_NAME + ' - ' + _("Not found") + ': ' + cgi.escape(str(c))), keywords="%s" % CFG_SITE_NAME, uid=uid, language=ln, req=req, navmenuid='search') # wash `aas' argument: if not os.path.exists("%s/collections/%d/body-as=%d-ln=%s.html" % \ (CFG_CACHEDIR, colID, aas, ln)): # nonexistent `aas' asked for, fall back to Simple Search: aas = 0 # display collection interface page: try: filedesc = open("%s/collections/%d/navtrail-as=%d-ln=%s.html" % \ (CFG_CACHEDIR, colID, aas, ln), "r") c_navtrail = filedesc.read() filedesc.close() except: c_navtrail = "" try: filedesc = open("%s/collections/%d/body-as=%d-ln=%s.html" % \ (CFG_CACHEDIR, colID, aas, ln), "r") c_body = filedesc.read() filedesc.close() except: c_body = "" try: filedesc = open("%s/collections/%d/portalbox-tp-ln=%s.html" % \ (CFG_CACHEDIR, colID, ln), "r") c_portalbox_tp = filedesc.read() filedesc.close() except: c_portalbox_tp = "" try: filedesc = open("%s/collections/%d/portalbox-te-ln=%s.html" % \ (CFG_CACHEDIR, colID, ln), "r") c_portalbox_te = filedesc.read() filedesc.close() except: c_portalbox_te = "" try: filedesc = open("%s/collections/%d/portalbox-lt-ln=%s.html" % \ (CFG_CACHEDIR, colID, ln), "r") c_portalbox_lt = filedesc.read() filedesc.close() except: c_portalbox_lt = "" try: # show help boxes (usually located in "tr", "top right") # if users have not banned them in their preferences: c_portalbox_rt = "" if user_preferences.get('websearch_helpbox', 1) > 0: filedesc = open("%s/collections/%d/portalbox-rt-ln=%s.html" % \ (CFG_CACHEDIR, colID, ln), "r") c_portalbox_rt = filedesc.read() filedesc.close() except: c_portalbox_rt = "" try: filedesc = open("%s/collections/%d/last-updated-ln=%s.html" % \ (CFG_CACHEDIR, colID, ln), "r") c_last_updated = filedesc.read() filedesc.close() except: c_last_updated = "" try: title = get_coll_i18nname(c, ln) except: title = "" show_title_p = True body_css_classes = [] if c == CFG_SITE_NAME: # Do not display title on home collection show_title_p = False body_css_classes.append('home') if len(collection_reclist_cache.cache.keys()) == 1: # if there is only one collection defined, do not print its # title on the page as it would be displayed repetitively. show_title_p = False if aas == -1: show_title_p = False # RSS: rssurl = CFG_SITE_URL + '/rss' rssurl_params = [] if c != CFG_SITE_NAME: rssurl_params.append('cc=' + quote(c)) if ln != CFG_SITE_LANG and \ c in CFG_WEBSEARCH_RSS_I18N_COLLECTIONS: rssurl_params.append('ln=' + ln) if rssurl_params: rssurl += '?' + '&'.join(rssurl_params) if 'hb' in CFG_WEBSEARCH_USE_JSMATH_FOR_FORMATS: metaheaderadd = """ """ else: metaheaderadd = '' return page(title=title, body=c_body, navtrail=c_navtrail, description="%s - %s" % (CFG_SITE_NAME, c), keywords="%s, %s" % (CFG_SITE_NAME, c), metaheaderadd=metaheaderadd, uid=uid, language=ln, req=req, cdspageboxlefttopadd=c_portalbox_lt, cdspageboxrighttopadd=c_portalbox_rt, titleprologue=c_portalbox_tp, titleepilogue=c_portalbox_te, lastupdated=c_last_updated, navmenuid='search', rssurl=rssurl, body_css_classes=body_css_classes, show_title_p=show_title_p) class WebInterfaceRSSFeedServicePages(WebInterfaceDirectory): """RSS 2.0 feed service pages.""" def __call__(self, req, form): """RSS 2.0 feed service.""" # Keep only interesting parameters for the search default_params = websearch_templates.rss_default_urlargd # We need to keep 'jrec' and 'rg' here in order to have # 'multi-page' RSS. These parameters are not kept be default # as we don't want to consider them when building RSS links # from search and browse pages. default_params.update({'jrec':(int, 1), 'rg': (int, CFG_WEBSEARCH_INSTANT_BROWSE_RSS)}) argd = wash_urlargd(form, default_params) user_info = collect_user_info(req) for coll in argd['c'] + [argd['cc']]: if collection_restricted_p(coll): (auth_code, auth_msg) = acc_authorize_action(user_info, VIEWRESTRCOLL, collection=coll) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : coll}) target = CFG_SITE_SECURE_URL + '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : CFG_SITE_URL + req.unparsed_uri}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg,\ navmenuid='search') # Create a standard filename with these parameters current_url = websearch_templates.build_rss_url(argd) cache_filename = current_url.split('/')[-1] # In the same way as previously, add 'jrec' & 'rg' req.content_type = "application/rss+xml" req.send_http_header() try: # Try to read from cache path = "%s/rss/%s.xml" % (CFG_CACHEDIR, cache_filename) # Check if cache needs refresh filedesc = open(path, "r") last_update_time = datetime.datetime.fromtimestamp(os.stat(os.path.abspath(path)).st_mtime) assert(datetime.datetime.now() < last_update_time + datetime.timedelta(minutes=CFG_WEBSEARCH_RSS_TTL)) c_rss = filedesc.read() filedesc.close() req.write(c_rss) return except Exception, e: # do it live and cache previous_url = None if argd['jrec'] > 1: prev_jrec = argd['jrec'] - argd['rg'] if prev_jrec < 1: prev_jrec = 1 previous_url = websearch_templates.build_rss_url(argd, jrec=prev_jrec) recIDs = perform_request_search(req, of="id", c=argd['c'], cc=argd['cc'], p=argd['p'], f=argd['f'], p1=argd['p1'], f1=argd['f1'], m1=argd['m1'], op1=argd['op1'], p2=argd['p2'], f2=argd['f2'], m2=argd['m2'], op2=argd['op2'], p3=argd['p3'], f3=argd['f3'], m3=argd['m3']) nb_found = len(recIDs) next_url = None if len(recIDs) >= argd['jrec'] + argd['rg']: next_url = websearch_templates.build_rss_url(argd, jrec=(argd['jrec'] + argd['rg'])) first_url = websearch_templates.build_rss_url(argd, jrec=1) last_url = websearch_templates.build_rss_url(argd, jrec=nb_found-argd['rg']+1) recIDs = recIDs[-argd['jrec']:(-argd['rg']-argd['jrec']):-1] rss_prologue = '\n' + \ websearch_templates.tmpl_xml_rss_prologue(current_url=current_url, previous_url=previous_url, next_url=next_url, first_url=first_url, last_url=last_url, nb_found=nb_found, jrec=argd['jrec'], rg=argd['rg']) + '\n' req.write(rss_prologue) rss_body = format_records(recIDs, of='xr', ln=argd['ln'], user_info=user_info, record_separator="\n", req=req, epilogue="\n") rss_epilogue = websearch_templates.tmpl_xml_rss_epilogue() + '\n' req.write(rss_epilogue) # update cache dirname = "%s/rss" % (CFG_CACHEDIR) mymkdir(dirname) fullfilename = "%s/rss/%s.xml" % (CFG_CACHEDIR, cache_filename) try: # Remove the file just in case it already existed # so that a bit of space is created os.remove(fullfilename) except OSError: pass # Check if there's enough space to cache the request. if len(os.listdir(dirname)) < CFG_WEBSEARCH_RSS_MAX_CACHED_REQUESTS: try: os.umask(022) f = open(fullfilename, "w") f.write(rss_prologue + rss_body + rss_epilogue) f.close() except IOError, v: if v[0] == 36: # URL was too long. Never mind, don't cache pass else: raise repr(v) index = __call__ class WebInterfaceRecordExport(WebInterfaceDirectory): """ Handling of a /record//export/ URL fragment """ _exports = output_formats def __init__(self, recid, format=None): self.recid = recid self.format = format for output_format in output_formats: self.__dict__[output_format] = self return def __call__(self, req, form): argd = wash_search_urlargd(form) argd['recid'] = self.recid if self.format is not None: argd['of'] = self.format req.argd = argd uid = getUid(req) if uid == -1: return page_not_authorized(req, "../", text="You are not authorized to view this record.", navmenuid='search') elif uid > 0: pref = get_user_preferences(uid) try: if not form.has_key('rg'): # fetch user rg preference only if not overridden via URL argd['rg'] = int(pref['websearch_group_records']) except (KeyError, ValueError): pass # Check if the record belongs to a restricted primary # collection. If yes, redirect to the authenticated URL. user_info = collect_user_info(req) (auth_code, auth_msg) = check_user_can_view_record(user_info, self.recid) if argd['rg'] > CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS and not isUserSuperAdmin(user_info): argd['rg'] = CFG_WEBSEARCH_MAX_RECORDS_IN_GROUPS - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = CFG_SITE_SECURE_URL + '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : argd['ln'], 'referer' : CFG_SITE_URL + req.unparsed_uri}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_msg,\ navmenuid='search') # mod_python does not like to return [] in case when of=id: out = perform_request_search(req, **argd) if out == []: return str(out) else: return out # Return the same page wether we ask for /record/123/export/xm or /record/123/export/xm/ index = __call__ diff --git a/modules/websession/lib/webaccount.py b/modules/websession/lib/webaccount.py index 7970e7c5b..779ac9405 100644 --- a/modules/websession/lib/webaccount.py +++ b/modules/websession/lib/webaccount.py @@ -1,433 +1,422 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. __revision__ = "$Id$" import re import MySQLdb import urllib from invenio.config import \ CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS, \ CFG_CERN_SITE, \ CFG_SITE_LANG, \ CFG_SITE_SUPPORT_EMAIL, \ CFG_SITE_ADMIN_EMAIL, \ CFG_SITE_SECURE_URL, \ CFG_VERSION, \ CFG_DATABASE_HOST, \ CFG_DATABASE_NAME from invenio.access_control_engine import acc_authorize_action -from invenio.access_control_config import CFG_EXTERNAL_AUTHENTICATION, SUPERADMINROLE +from invenio.access_control_config import CFG_EXTERNAL_AUTHENTICATION, \ + SUPERADMINROLE, CFG_EXTERNAL_AUTH_DEFAULT from invenio.dbquery import run_sql from invenio.webuser import getUid,isGuestUser, get_user_preferences, \ collect_user_info from invenio.access_control_admin import acc_find_user_role_actions from invenio.messages import gettext_set_language from invenio.external_authentication import InvenioWebAccessExternalAuthError import invenio.template websession_templates = invenio.template.load('websession') def perform_info(req, ln): """Display the main features of CDS personalize""" out = "" uid = getUid(req) user_info = collect_user_info(req) return websession_templates.tmpl_account_info( ln = ln, uid = uid, guest = isGuestUser(uid), CFG_CERN_SITE = CFG_CERN_SITE, ) def perform_display_external_user_settings(settings, ln): """show external user settings which is a dictionary.""" _ = gettext_set_language(ln) html_settings = "" print_settings = False settings_keys = settings.keys() settings_keys.sort() for key in settings_keys: value = settings[key] if key.startswith("EXTERNAL_") and not "HIDDEN_" in key: print_settings = True key = key[9:].capitalize() html_settings += websession_templates.tmpl_external_setting(ln, key, value) return print_settings and websession_templates.tmpl_external_user_settings(ln, html_settings) or "" def perform_youradminactivities(user_info, ln): """Return text for the `Your Admin Activities' box. Analyze whether user UID has some admin roles, and if yes, then print suitable links for the actions he can do. If he's not admin, print a simple non-authorized message.""" your_role_actions = acc_find_user_role_actions(user_info) your_roles = [] your_admin_activities = [] guest = isGuestUser(user_info['uid']) for (role, action) in your_role_actions: if role not in your_roles: your_roles.append(role) if action not in your_admin_activities: your_admin_activities.append(action) if SUPERADMINROLE in your_roles: for action in ("runbibedit", "cfgbibformat", "cfgbibharvest", "cfgoairepository", "cfgbibrank", "cfgbibindex", "cfgwebaccess", "cfgwebcomment", "cfgwebsearch", "cfgwebsubmiit", "cfgbibknowledge", "runbatchuploader"): if action not in your_admin_activities: your_admin_activities.append(action) return websession_templates.tmpl_account_adminactivities( ln = ln, uid = user_info['uid'], guest = guest, roles = your_roles, activities = your_admin_activities, ) def perform_display_account(req, username, bask, aler, sear, msgs, loan, grps, sbms, appr, admn, ln): """Display a dynamic page that shows the user's account.""" # load the right message language _ = gettext_set_language(ln) uid = getUid(req) user_info = collect_user_info(req) #your account if isGuestUser(uid): user = "guest" login = "%s/youraccount/login?ln=%s" % (CFG_SITE_SECURE_URL, ln) accBody = _("You are logged in as guest. You may want to %(x_url_open)slogin%(x_url_close)s as a regular user.") %\ {'x_url_open': '', 'x_url_close': ''} accBody += "

    " bask=aler=msgs= _("The %(x_fmt_open)sguest%(x_fmt_close)s users need to %(x_url_open)sregister%(x_url_close)s first") %\ {'x_fmt_open': '', 'x_fmt_close': '', 'x_url_open': '', 'x_url_close': ''} sear= _("No queries found") else: user = username accBody = websession_templates.tmpl_account_body( ln = ln, user = user, ) #Display warnings if user is superuser roles = acc_find_user_role_actions(user_info) warnings = "0" for role in roles: if "superadmin" in role: warnings = "1" break warning_list = superuser_account_warnings() #check if tickets ok tickets = (acc_authorize_action(user_info, 'runbibedit')[0] == 0) return websession_templates.tmpl_account_page( ln = ln, warnings = warnings, warning_list = warning_list, accBody = accBody, baskets = bask, alerts = aler, searches = sear, messages = msgs, loans = loan, groups = grps, submissions = sbms, approvals = appr, tickets = tickets, administrative = admn ) def superuser_account_warnings(): """Check to see whether admin accounts have default / blank password etc. Returns a list""" warning_array = [] #Try and connect to the mysql database with the default invenio password try: conn = MySQLdb.connect (host = CFG_DATABASE_HOST, user = "root", passwd = "my123p$ss", db = "mysql") conn.close() warning_array.append("warning_mysql_password_equal_to_invenio_password") except: pass #Try and connect to the invenio database with the default invenio password try: conn = MySQLdb.connect (host = CFG_DATABASE_HOST, user = "invenio", passwd = "my123p$ss", db = CFG_DATABASE_NAME) conn.close () warning_array.append("warning_invenio_password_equal_to_default") except: pass #Check if the admin password is empty res = run_sql("SELECT password, email from user where nickname = 'admin'") res1 = run_sql("SELECT email from user where nickname = 'admin' and password = AES_ENCRYPT(%s,'')", (res[0][1], )) for user in res1: warning_array.append("warning_empty_admin_password") #Check if the admin email has been changed from the default if (CFG_SITE_ADMIN_EMAIL == "info@invenio-software.org" or CFG_SITE_SUPPORT_EMAIL == "info@invenio-software.org") and CFG_CERN_SITE == 0: warning_array.append("warning_site_support_email_equal_to_default") #Check for a new release of Invenio try: find = re.compile('Invenio v[0-9]+.[0-9]+.[0-9]+ is released') webFile = urllib.urlopen("http://cdsware.cern.ch/download/RELEASE-NOTES") temp = "" version = "" version1 = "" while 1: temp = webFile.readline() match1 = find.match(temp) try: version = match1.group() break except: pass if not temp: break webFile.close() submatch = re.compile('[0-9]+.[0-9]+.[0-9]+') version1 = submatch.search(version) web_version = version1.group().split(".") local_version = CFG_VERSION.split(".") if web_version[0] > local_version[0]: warning_array.append("note_new_release_available") elif web_version[0] == local_version[0] and web_version[1] > local_version[1]: warning_array.append("note_new_release_available") elif web_version[0] == local_version[0] and web_version[1] == local_version[1] and web_version[2] > local_version[2]: warning_array.append("note_new_release_available") except: warning_array.append("error_cannot_download_release_notes") return warning_array def template_account(title, body, ln): """It is a template for print each of the options from the user's account.""" return websession_templates.tmpl_account_template( ln = ln, title = title, body = body ) def warning_guest_user(type, ln=CFG_SITE_LANG): """It returns an alert message,showing that the user is a guest user and should log into the system.""" # load the right message language _ = gettext_set_language(ln) return websession_templates.tmpl_warning_guest_user( ln = ln, type = type, ) def perform_delete(ln): """Delete the account of the user, not implement yet.""" # TODO return websession_templates.tmpl_account_delete(ln = ln) def perform_set(email, ln, can_config_bibcatalog = False, verbose = 0): """Perform_set(email,password): edit your account parameters, email and password. If can_config_bibcatalog is True, show the bibcatalog dialog (if configured). """ try: res = run_sql("SELECT id, nickname FROM user WHERE email=%s", (email,)) uid = res[0][0] nickname = res[0][1] except: uid = 0 nickname = "" CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS_LOCAL = CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS prefs = get_user_preferences(uid) - if CFG_EXTERNAL_AUTHENTICATION.has_key(prefs['login_method']) and CFG_EXTERNAL_AUTHENTICATION[prefs['login_method']][0]: + if CFG_EXTERNAL_AUTHENTICATION.has_key(prefs['login_method']) and CFG_EXTERNAL_AUTHENTICATION[prefs['login_method']] is not None: CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS_LOCAL = 3 out = websession_templates.tmpl_user_preferences( ln = ln, email = email, email_disabled = (CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS_LOCAL >= 2), password_disabled = (CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS_LOCAL >= 3), nickname = nickname, ) if len(CFG_EXTERNAL_AUTHENTICATION) > 1: try: uid = run_sql("SELECT id FROM user where email=%s", (email,)) uid = uid[0][0] except: uid = 0 current_login_method = prefs['login_method'] methods = CFG_EXTERNAL_AUTHENTICATION.keys() # Filtering out methods that don't provide user_exists to check if # a user exists in the external auth method before letting him/her # to switch. for method in methods: - if CFG_EXTERNAL_AUTHENTICATION[method][0]: + if CFG_EXTERNAL_AUTHENTICATION[method] is not None: try: - if not CFG_EXTERNAL_AUTHENTICATION[method][0].user_exists(email): + if not CFG_EXTERNAL_AUTHENTICATION[method].user_exists(email): methods.remove(method) - except (AttributeError, InvenioWebAccessExternalAuthError): + except (AttributeError, InvenioWebAccessExternalAuthError, NotImplementedError): methods.remove(method) methods.sort() if len(methods) > 1: out += websession_templates.tmpl_user_external_auth( ln = ln, methods = methods, current = current_login_method, method_disabled = (CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 4) ) current_group_records = prefs.get('websearch_group_records', 10) show_latestbox = prefs.get('websearch_latestbox', True) show_helpbox = prefs.get('websearch_helpbox', True) out += websession_templates.tmpl_user_websearch_edit( ln = ln, current = current_group_records, show_latestbox = show_latestbox, show_helpbox = show_helpbox, ) preferred_lang = prefs.get('language', ln) out += websession_templates.tmpl_user_lang_edit( ln = ln, preferred_lang = preferred_lang ) #show this dialog only if the system has been configured to use a ticket system from invenio.config import CFG_BIBCATALOG_SYSTEM if CFG_BIBCATALOG_SYSTEM and can_config_bibcatalog: bibcatalog_username = prefs.get('bibcatalog_username', "") bibcatalog_password = prefs.get('bibcatalog_password', "") out += websession_templates.tmpl_user_bibcatalog_auth(bibcatalog_username, \ bibcatalog_password, ln=ln) if verbose >= 9: for key, value in prefs.items(): out += "%s:%s
    " % (key, value) out += perform_display_external_user_settings(prefs, ln) return out def create_register_page_box(referer='', ln=CFG_SITE_LANG): """Register a new account.""" return websession_templates.tmpl_register_page( referer = referer, ln = ln, level = CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS, ) ## create_login_page_box(): ask for the user's email and password, for login into the system -def create_login_page_box(referer='', apache_msg="", ln=CFG_SITE_LANG): +def create_login_page_box(referer='', ln=CFG_SITE_LANG): # List of referer regexep and message to print _ = gettext_set_language(ln) login_referrer2msg = ( (re.compile(r"/search"), "

    " + _("This collection is restricted. If you think you have right to access it, please authenticate yourself.") + "

    "), (re.compile(r"/record/\d+/files/.+"), "

    " + _("This file is restricted. If you think you have right to access it, please authenticate yourself.") + "

    "), ) msg = "" for regexp, txt in login_referrer2msg: if regexp.search(referer): msg = txt break - # FIXME: Temporary Hack to help CDS current migration - if CFG_CERN_SITE and apache_msg: - return msg + apache_msg - - if apache_msg: - msg += apache_msg + "

    2) Otherwise please authenticate yourself" \ - " in the following form:

    " - internal = None for system in CFG_EXTERNAL_AUTHENTICATION.keys(): - if not CFG_EXTERNAL_AUTHENTICATION[system][0]: + if CFG_EXTERNAL_AUTHENTICATION[system] is None: internal = system break register_available = CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS <= 1 and internal - methods = CFG_EXTERNAL_AUTHENTICATION.keys() + ## Let's retrieve all the login method that are not dedicated to robots + methods = [method[0] for method in CFG_EXTERNAL_AUTHENTICATION.iteritems() if not method[1] or not method[1].robot_login_method_p()] methods.sort() - selected = '' - for method in methods: - if CFG_EXTERNAL_AUTHENTICATION[method][1]: - selected = method - break return websession_templates.tmpl_login_form( ln = ln, referer = referer, internal = internal, register_available = register_available, methods = methods, - selected_method = selected, + selected_method = CFG_EXTERNAL_AUTH_DEFAULT, msg = msg, ) # perform_logout: display the message of not longer authorized, def perform_logout(req, ln): return websession_templates.tmpl_account_logout(ln = ln) #def perform_lost: ask the user for his email, in order to send him the lost password def perform_lost(ln): return websession_templates.tmpl_lost_password_form(ln) #def perform_reset_password: ask the user for a new password to reset the lost one def perform_reset_password(ln, email, reset_key, msg=''): return websession_templates.tmpl_reset_password_form(ln, email, reset_key, msg) # perform_emailSent(email): confirm that the password has been emailed to 'email' address def perform_emailSent(email, ln): return websession_templates.tmpl_account_emailSent(ln = ln, email = email) # peform_emailMessage : display a error message when the email introduced is not correct, and sugest to try again def perform_emailMessage(eMsg, ln): return websession_templates.tmpl_account_emailMessage( ln = ln, msg = eMsg ) # perform_back(): template for return to a previous page, used for login,register and setting def perform_back(mess, url, linkname, ln='en'): return websession_templates.tmpl_back_form( ln = ln, message = mess, url = url, link = linkname, ) diff --git a/modules/websession/lib/webgroup.py b/modules/websession/lib/webgroup.py index 707988d79..ca678c47b 100644 --- a/modules/websession/lib/webgroup.py +++ b/modules/websession/lib/webgroup.py @@ -1,883 +1,883 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Group features.""" __revision__ = "$Id$" import sys from invenio.config import CFG_SITE_LANG from invenio.messages import gettext_set_language from invenio.websession_config import CFG_WEBSESSION_INFO_MESSAGES, \ CFG_WEBSESSION_USERGROUP_STATUS, \ CFG_WEBSESSION_GROUP_JOIN_POLICY from invenio.webuser import nickname_valid_p, get_user_info from invenio.webmessage import perform_request_send import invenio.webgroup_dblayer as db from invenio.dbquery import IntegrityError try: import invenio.template websession_templates = invenio.template.load('websession') except ImportError: pass if sys.hexversion < 0x2040000: # pylint: disable=W0622 from sets import Set as set # pylint: enable=W0622 def perform_request_groups_display(uid, infos=[], errors = [], warnings = [], \ ln=CFG_SITE_LANG): """Display all the groups the user belongs to. @param uid: user id @param info: info about last user action @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ _ = gettext_set_language(ln) body = "" (body_admin, errors_admin) = display_admin_groups(uid, ln) (body_member, errors_member) = display_member_groups(uid, ln) (body_external, errors_external) = display_external_groups(uid, ln) if errors_admin: errors.extend(errors_admin) if errors_member: errors.extend(errors_member) if errors_external: errors.extend(errors_external) body = websession_templates.tmpl_display_all_groups(infos=infos, admin_group_html=body_admin, member_group_html=body_member, external_group_html=body_external, warnings=warnings, ln=ln) return (body, errors, warnings) def display_admin_groups(uid, ln=CFG_SITE_LANG): """Display groups the user is admin of. @param uid: user id @param ln: language @return: a (body, errors[]) formed tuple return html groups representation the user is admin of """ body = "" errors = [] record = db.get_groups_by_user_status(uid=uid, user_status=CFG_WEBSESSION_USERGROUP_STATUS["ADMIN"]) body = websession_templates.tmpl_display_admin_groups(groups=record, ln=ln) return (body, errors) def display_member_groups(uid, ln=CFG_SITE_LANG): """Display groups the user is member of. @param uid: user id @param ln: language @return: a (body, errors[]) formed tuple body : html groups representation the user is member of """ body = "" errors = [] records = db.get_groups_by_user_status(uid, user_status=CFG_WEBSESSION_USERGROUP_STATUS["MEMBER"] ) body = websession_templates.tmpl_display_member_groups(groups=records, ln=ln) return (body, errors) def display_external_groups(uid, ln=CFG_SITE_LANG): """Display groups the user is admin of. @param uid: user id @param ln: language @return: a (body, errors[]) formed tuple return html groups representation the user is admin of """ body = "" errors = [] record = db.get_external_groups(uid) if record: body = websession_templates.tmpl_display_external_groups(groups=record, ln=ln) else: body = None return (body, errors) def perform_request_input_create_group(group_name, group_description, join_policy, warnings=[], ln=CFG_SITE_LANG): """Display form for creating new group. @param group_name: name of the group entered if the page has been reloaded @param group_description: description entered if the page has been reloaded @param join_policy: join policy chosen if the page has been reloaded @param warnings: warnings @param ln: language @return: a (body, errors[], warnings[]) formed tuple body: html for group creation page """ body = "" errors = [] body = websession_templates.tmpl_display_input_group_info(group_name, group_description, join_policy, act_type="create", warnings=warnings, ln=ln) return (body, errors, warnings) def perform_request_create_group(uid, group_name, group_description, join_policy, ln=CFG_SITE_LANG): """Create new group. @param group_name: name of the group entered @param group_description: description of the group entered @param join_policy: join policy of the group entered @param ln: language @return: a (body, errors, warnings) formed tuple warning != [] if group_name or join_policy are not valid or if the name already exists in the database body="1" if succeed in order to display info on the main page """ _ = gettext_set_language(ln) body = "" warnings = [] errors = [] infos = [] if group_name == "": warnings.append(('WRN_WEBSESSION_NO_GROUP_NAME',)) (body, errors, warnings) = perform_request_input_create_group( group_name, group_description, join_policy, warnings=warnings) elif not group_name_valid_p(group_name): warnings.append('WRN_WEBSESSION_NOT_VALID_GROUP_NAME') (body, errors, warnings) = perform_request_input_create_group( group_name, group_description, join_policy, warnings=warnings) elif join_policy=="-1": warnings.append('WRN_WEBSESSION_NO_JOIN_POLICY') (body, errors, warnings) = perform_request_input_create_group( group_name, group_description, join_policy, warnings=warnings) elif db.group_name_exist(group_name): warnings.append('WRN_WEBSESSION_GROUP_NAME_EXISTS') (body, errors, warnings) = perform_request_input_create_group( group_name, group_description, join_policy, warnings=warnings) else: db.insert_new_group(uid, group_name, group_description, join_policy) infos.append(CFG_WEBSESSION_INFO_MESSAGES["GROUP_CREATED"]) (body, errors, warnings) = perform_request_groups_display(uid, infos=infos, errors=errors, warnings=warnings, ln=ln) return (body, errors, warnings) def perform_request_input_join_group(uid, group_name, search, warnings=[], ln=CFG_SITE_LANG): """Return html for joining new group. @param group_name: name of the group entered if user is looking for a group @param search=1 if search performed else 0 @param warnings: warnings coming from perform_request_join_group @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ errors = [] group_from_search = {} records = db.get_visible_group_list(uid=uid) if search: group_from_search = db.get_visible_group_list(uid, group_name) body = websession_templates.tmpl_display_input_join_group(records.items(), group_name, group_from_search.items(), search, warnings=warnings, ln=ln) return (body, errors, warnings) def perform_request_join_group(uid, grpID, group_name, search, ln=CFG_SITE_LANG): """Join group. @param grpID: list of the groups the user wants to join, only one value must be selected among the two group lists (default group list, group list resulting from the search) @param group_name: name of the group entered if search on name performed @param search=1 if search performed else 0 @param ln: language @return: a (body, errors[], warnings[]) formed tuple warnings != [] if 0 or more than one group is selected """ _ = gettext_set_language(ln) body = "" warnings = [] errors = [] infos = [] if "-1" in grpID: grpID = filter(lambda x: x != '-1', grpID) if len(grpID)==1 : grpID = int(grpID[0]) # test if user is already member or pending status = db.get_user_status(uid, grpID) if status: warnings.append('WRN_WEBSESSION_ALREADY_MEMBER') (body, errors, warnings) = perform_request_groups_display(uid, infos=infos, errors=errors, warnings=warnings, ln=ln) # insert new user of group else: group_infos = db.get_group_infos(grpID) group_type = group_infos[0][3] if group_type == CFG_WEBSESSION_GROUP_JOIN_POLICY["VISIBLEMAIL"]: db.insert_new_member(uid, grpID, CFG_WEBSESSION_USERGROUP_STATUS["PENDING"]) admin = db.get_users_by_status(grpID, CFG_WEBSESSION_USERGROUP_STATUS["ADMIN"])[0][1] group_name = group_infos[0][1] msg_subjet, msg_body = websession_templates.tmpl_admin_msg( group_name=group_name, grpID=grpID, ln=ln) (body, errors, warnings, dummy, dummy) = \ perform_request_send(uid, msg_to_user=admin, msg_to_group="", msg_subject=msg_subjet, msg_body=msg_body, ln=ln) infos.append(CFG_WEBSESSION_INFO_MESSAGES["JOIN_REQUEST"]) elif group_type == CFG_WEBSESSION_GROUP_JOIN_POLICY["VISIBLEOPEN"]: db.insert_new_member(uid, grpID, CFG_WEBSESSION_USERGROUP_STATUS["MEMBER"]) infos.append(CFG_WEBSESSION_INFO_MESSAGES["JOIN_GROUP"]) (body, errors, warnings) = perform_request_groups_display(uid, infos=infos, errors=errors, warnings=warnings, ln=ln) else: warnings.append('WRN_WEBSESSION_MULTIPLE_GROUPS') (body, errors, warnings) = perform_request_input_join_group(uid, group_name, search, warnings, ln) return (body, errors, warnings) def perform_request_input_leave_group(uid, warnings=[], ln=CFG_SITE_LANG): """Return html for leaving group. @param uid: user ID @param warnings: warnings != [] if 0 group is selected or if not admin of the @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ body = "" errors = [] groups = [] records = db.get_groups_by_user_status(uid=uid, user_status=CFG_WEBSESSION_USERGROUP_STATUS["MEMBER"]) map(lambda x: groups.append((x[0], x[1])), records) body = websession_templates.tmpl_display_input_leave_group(groups, warnings=warnings, ln=ln) return (body, errors, warnings) def perform_request_leave_group(uid, grpID, confirmed=0, ln=CFG_SITE_LANG): """Leave group. @param uid: user ID @param grpID: ID of the group the user wants to leave @param warnings: warnings != [] if 0 group is selected @param confirmed: a confirmed page is first displayed @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ _ = gettext_set_language(ln) body = "" warnings = [] errors = [] infos = [] if not grpID == -1: if confirmed: db.leave_group(grpID, uid) infos.append(CFG_WEBSESSION_INFO_MESSAGES["LEAVE_GROUP"]) (body, errors, warnings) = perform_request_groups_display(uid, infos=infos, errors=errors, warnings=warnings, ln=ln) else: body = websession_templates.tmpl_confirm_leave(uid, grpID, ln) else: warnings.append('WRN_WEBSESSION_NO_GROUP_SELECTED') (body, errors, warnings) = perform_request_input_leave_group(uid, warnings= warnings, ln=ln) return (body, errors, warnings) def perform_request_edit_group(uid, grpID, warnings=[], ln=CFG_SITE_LANG): """Return html for group editing. @param uid: user ID @param grpID: ID of the group @param warnings: warnings @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ body = '' errors = [] user_status = db.get_user_status(uid, grpID) if not len(user_status): errors.append('ERR_WEBSESSION_DB_ERROR') return (body, errors, warnings) elif user_status[0][0] != CFG_WEBSESSION_USERGROUP_STATUS['ADMIN']: errors.append(('ERR_WEBSESSION_GROUP_NO_RIGHTS',)) return (body, errors, warnings) group_infos = db.get_group_infos(grpID)[0] if not len(group_infos): errors.append('ERR_WEBSESSION_DB_ERROR') return (body, errors, warnings) body = websession_templates.tmpl_display_input_group_info( group_name=group_infos[1], group_description=group_infos[2], join_policy=group_infos[3], act_type="update", grpID=grpID, warnings=warnings, ln=ln) return (body, errors, warnings) def perform_request_update_group(uid, grpID, group_name, group_description, join_policy, ln=CFG_SITE_LANG): """Update group datas in database. @param uid: user ID @param grpID: ID of the group @param group_name: name of the group @param group_description: description of the group @param join_policy: join policy of the group @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ body = '' errors = [] warnings = [] infos = [] _ = gettext_set_language(ln) group_name_available = db.group_name_exist(group_name) if group_name == "": warnings.append('WRN_WEBSESSION_NO_GROUP_NAME') (body, errors, warnings) = perform_request_edit_group(uid, grpID, warnings=warnings, ln=ln) elif not group_name_valid_p(group_name): warnings.append('WRN_WEBSESSION_NOT_VALID_GROUP_NAME') (body, errors, warnings) = perform_request_edit_group(uid, grpID, warnings=warnings, ln=ln) elif join_policy == "-1": warnings.append('WRN_WEBSESSION_NO_JOIN_POLICY') (body, errors, warnings) = perform_request_edit_group(uid, grpID, warnings=warnings, ln=ln) elif (group_name_available and group_name_available[0][0]!= grpID): warnings.append('WRN_WEBSESSION_GROUP_NAME_EXISTS') (body, errors, warnings) = perform_request_edit_group(uid, grpID, warnings=warnings, ln=ln) else: grpID = db.update_group_infos(grpID, group_name, group_description, join_policy) infos.append(CFG_WEBSESSION_INFO_MESSAGES["GROUP_UPDATED"]) (body, errors, warnings) = perform_request_groups_display(uid, infos=infos, errors=errors, warnings=warnings, ln=CFG_SITE_LANG) return (body, errors, warnings) def perform_request_delete_group(uid, grpID, confirmed=0, ln=CFG_SITE_LANG): """First display confirm message(confirmed=0). then(confirmed=1) delete group and all its members @param uid: user ID @param grpID: ID of the group @param confirmed: =1 if confirmed message has been previously displayed @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ body = "" warnings = [] errors = [] infos = [] _ = gettext_set_language(ln) group_infos = db.get_group_infos(grpID) user_status = db.get_user_status(uid, grpID) if not group_infos: warnings.append('WRN_WEBSESSION_GROUP_ALREADY_DELETED') (body, errors, warnings) = perform_request_groups_display(uid, infos=infos, errors=errors, warnings=warnings, ln=CFG_SITE_LANG) else: if not len(user_status): errors.append('ERR_WEBSESSION_DB_ERROR') elif confirmed: group_infos = db.get_group_infos(grpID) group_name = group_infos[0][1] msg_subjet, msg_body = websession_templates.tmpl_delete_msg( group_name=group_name, ln=ln) (body, errors, warnings, dummy, dummy) = perform_request_send( uid, msg_to_user="", msg_to_group=group_name, msg_subject=msg_subjet, msg_body=msg_body, ln=ln) db.delete_group_and_members(grpID) infos.append(CFG_WEBSESSION_INFO_MESSAGES["GROUP_DELETED"]) (body, errors, warnings) = perform_request_groups_display(uid, infos=infos, errors=errors, warnings=warnings, ln=CFG_SITE_LANG) else: body = websession_templates.tmpl_confirm_delete(grpID, ln) return (body, errors, warnings) def perform_request_manage_member(uid, grpID, infos=[], warnings=[], ln=CFG_SITE_LANG): """Return html for managing group's members. @param uid: user ID @param grpID: ID of the group @param info: info about last user action @param warnings: warnings @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ body = '' errors = [] _ = gettext_set_language(ln) user_status = db.get_user_status(uid, grpID) if not len(user_status): errors.append('ERR_WEBSESSION_DB_ERROR') return (body, errors, warnings) elif user_status[0][0] != CFG_WEBSESSION_USERGROUP_STATUS['ADMIN']: errors.append(('ERR_WEBSESSION_GROUP_NO_RIGHTS',)) return (body, errors, warnings) group_infos = db.get_group_infos(grpID) if not len(group_infos): errors.append('ERR_WEBSESSION_DB_ERROR') return (body, errors, warnings) members = db.get_users_by_status(grpID, CFG_WEBSESSION_USERGROUP_STATUS["MEMBER"]) pending_members = db.get_users_by_status(grpID, CFG_WEBSESSION_USERGROUP_STATUS["PENDING"]) body = websession_templates.tmpl_display_manage_member(grpID=grpID, group_name=group_infos[0][1], members=members, pending_members=pending_members, warnings=warnings, infos=infos, ln=ln) return (body, errors, warnings) def perform_request_remove_member(uid, grpID, member_id, ln=CFG_SITE_LANG): """Remove member from a group. @param uid: user ID @param grpID: ID of the group @param member_id: selected member ID @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ body = '' errors = [] warnings = [] infos = [] _ = gettext_set_language(ln) user_status = db.get_user_status(uid, grpID) if not len(user_status): errors.append('ERR_WEBSESSION_DB_ERROR') return (body, errors, warnings) if member_id == -1: warnings.append('WRN_WEBSESSION_NO_MEMBER_SELECTED') (body, errors, warnings) = perform_request_manage_member(uid, grpID, warnings=warnings, ln=ln) else: db.delete_member(grpID, member_id) infos.append(CFG_WEBSESSION_INFO_MESSAGES["MEMBER_DELETED"]) (body, errors, warnings) = perform_request_manage_member(uid, grpID, infos=infos, warnings=warnings, ln=ln) return (body, errors, warnings) def perform_request_add_member(uid, grpID, user_id, ln=CFG_SITE_LANG): """Add waiting member to a group. @param uid: user ID @param grpID: ID of the group @param user_id: selected member ID @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ body = '' errors = [] warnings = [] infos = [] _ = gettext_set_language(ln) user_status = db.get_user_status(uid, grpID) if not len(user_status): errors.append('ERR_WEBSESSION_DB_ERROR') return (body, errors, warnings) if user_id == -1: warnings.append('WRN_WEBSESSION_NO_USER_SELECTED_ADD') (body, errors, warnings) = perform_request_manage_member(uid, grpID, warnings=warnings, ln=ln) else : # test if user is already member or pending status = db.get_user_status(user_id, grpID) if status and status[0][0] == 'M': warnings.append('WRN_WEBSESSION_ALREADY_MEMBER_ADD') (body, errors, warnings) = perform_request_manage_member(uid, grpID, infos=infos, warnings=warnings, ln=ln) else: db.add_pending_member(grpID, user_id, CFG_WEBSESSION_USERGROUP_STATUS["MEMBER"]) infos.append(CFG_WEBSESSION_INFO_MESSAGES["MEMBER_ADDED"]) group_infos = db.get_group_infos(grpID) group_name = group_infos[0][1] user = get_user_info(user_id, ln)[2] msg_subjet, msg_body = websession_templates.tmpl_member_msg( group_name=group_name, accepted=1, ln=ln) (body, errors, warnings, dummy, dummy) = perform_request_send( uid, msg_to_user=user, msg_to_group="", msg_subject=msg_subjet, msg_body=msg_body, ln=ln) (body, errors, warnings) = perform_request_manage_member(uid, grpID, infos=infos, warnings=warnings, ln=ln) return (body, errors, warnings) def perform_request_reject_member(uid, grpID, user_id, ln=CFG_SITE_LANG): """Reject waiting member and delete it from the list. @param uid: user ID @param grpID: ID of the group @param member_id: selected member ID @param ln: language @return: a (body, errors[], warnings[]) formed tuple """ body = '' errors = [] warnings = [] infos = [] _ = gettext_set_language(ln) user_status = db.get_user_status(uid, grpID) if not len(user_status): errors.append('ERR_WEBSESSION_DB_ERROR') return (body, errors, warnings) if user_id == -1: warnings.append('WRN_WEBSESSION_NO_USER_SELECTED_DEL') (body, errors, warnings) = perform_request_manage_member(uid, grpID, warnings=warnings, ln=ln) else : # test if user is already member or pending status = db.get_user_status(user_id, grpID) if not status: warnings.append('WRN_WEBSESSION_ALREADY_MEMBER_REJECT') (body, errors, warnings) = perform_request_manage_member(uid, grpID, infos=infos, warnings=warnings, ln=ln) else: db.delete_member(grpID, user_id) group_infos = db.get_group_infos(grpID) group_name = group_infos[0][1] user = get_user_info(user_id, ln)[2] msg_subjet, msg_body = websession_templates.tmpl_member_msg( group_name=group_name, accepted=0, ln=ln) (body, errors, warnings, dummy, dummy) = perform_request_send( uid, msg_to_user=user, msg_to_group="", msg_subject=msg_subjet, msg_body=msg_body, ln=ln) infos.append(CFG_WEBSESSION_INFO_MESSAGES["MEMBER_REJECTED"]) (body, errors, warnings) = perform_request_manage_member(uid, grpID, infos=infos, warnings=warnings, ln=ln) return (body, errors, warnings) def account_group(uid, ln=CFG_SITE_LANG): """Display group info for myaccount.py page. @param uid: user id (int) @param ln: language @return: html body """ nb_admin_groups = db.count_nb_group_user(uid, CFG_WEBSESSION_USERGROUP_STATUS["ADMIN"]) nb_member_groups = db.count_nb_group_user(uid, CFG_WEBSESSION_USERGROUP_STATUS["MEMBER"]) nb_total_groups = nb_admin_groups + nb_member_groups return websession_templates.tmpl_group_info(nb_admin_groups, nb_member_groups, nb_total_groups, ln=ln) def get_navtrail(ln=CFG_SITE_LANG, title=""): """Gets the navtrail for title. @param title: title of the page @param ln: language @return: HTML output """ navtrail = websession_templates.tmpl_navtrail(ln, title) return navtrail def synchronize_external_groups(userid, groups, login_method): """Synchronize external groups adding new groups that aren't already added, adding subscription for userid to groups, for groups that the user isn't already subsribed to, removing subscription to groups the user is no more subsribed to. @param userid, the intger representing the user inside the db @param groups, a dictionary of group_name : group_description @param login_method, a string unique to the type of authentication the groups are associated to, to be used inside the db """ groups_already_known = db.get_login_method_groups(userid, login_method) group_dict = {} for name, groupid in groups_already_known: group_dict[name] = groupid groups_already_known_name = set([g[0] for g in groups_already_known]) groups_name = set(groups.keys()) nomore_groups = groups_already_known_name - groups_name for group in nomore_groups: # delete the user from no more affiliated group db.delete_member(group_dict[group], userid) potential_new_groups = groups_name - groups_already_known_name for group in potential_new_groups: groupid = db.get_group_id(group, login_method) if groupid: # Adding the user to an already existent group db.insert_new_member(userid, groupid[0][0], CFG_WEBSESSION_USERGROUP_STATUS['MEMBER']) else: # Adding a new group try: groupid = db.insert_new_group(userid, group, groups[group], \ CFG_WEBSESSION_GROUP_JOIN_POLICY['VISIBLEEXTERNAL'], \ login_method) db.add_pending_member(groupid, userid, CFG_WEBSESSION_USERGROUP_STATUS['MEMBER']) except IntegrityError: ## The group already exists? Maybe because of concurrency? groupid = db.get_group_id(group, login_method) if groupid: # Adding the user to an already existent group db.insert_new_member(userid, groupid[0][0], CFG_WEBSESSION_USERGROUP_STATUS['MEMBER']) def synchronize_groups_with_login_method(): """For each login_method, if possible, synchronize groups in a bulk fashion (i.e. when fetch_all_users_groups_membership is implemented in the external_authentication class). Otherwise, for each user that belong to at least one external group for a given login_method, ask, if possible, for his group memberships and merge them. """ from invenio.access_control_config import CFG_EXTERNAL_AUTHENTICATION for login_method, authorizer in CFG_EXTERNAL_AUTHENTICATION.items(): - if authorizer[0]: + if authorizer: try: - usersgroups = authorizer[0].fetch_all_users_groups_membership() + usersgroups = authorizer.fetch_all_users_groups_membership() synchronize_all_external_groups(usersgroups, login_method) except (NotImplementedError, NameError): users = db.get_all_users_with_groups_with_login_method( login_method) for email, uid in users.items(): try: - groups = authorizer[0].fetch_user_groups_membership( + groups = authorizer.fetch_user_groups_membership( email) synchronize_external_groups(uid, groups, login_method) except (NotImplementedError, NameError): pass def synchronize_all_external_groups(usersgroups, login_method): """Merges all the groups vs users memberships. @param usersgroups: is {'mygroup': ('description', ['email1', 'email2', ...]), ...} @return: True in case everythings is ok, False otherwise """ db_users = db.get_all_users() # All users of the database {email:uid, ...} db_users_set = set(db_users.keys()) # Set of all users set('email1', # 'email2', ...) for key, value in usersgroups.items(): # cleaning users not in db cleaned_user_list = set() for username in value[1]: username = username.upper() if username in db_users_set: cleaned_user_list.add(db_users[username]) if cleaned_user_list: usersgroups[key] = (value[0], cleaned_user_list) else: # List of user now is empty del usersgroups[key] # cleaning not interesting groups # now for each group we got a description and a set of uid groups_already_known = db.get_all_login_method_groups(login_method) # groups in the db {groupname: id} groups_already_known_set = set(groups_already_known.keys()) # set of the groupnames in db usersgroups_set = set(usersgroups.keys()) # set of groupnames to be merged # deleted groups! nomore_groups = groups_already_known_set - usersgroups_set for group_name in nomore_groups: db.delete_group_and_members(groups_already_known[group_name]) # new groups! new_groups = usersgroups_set - groups_already_known_set for group_name in new_groups: groupid = db.insert_only_new_group( group_name, usersgroups[group_name][0], # description CFG_WEBSESSION_GROUP_JOIN_POLICY['VISIBLEEXTERNAL'], login_method) for uid in usersgroups[group_name][1]: db.insert_new_member(uid, groupid, CFG_WEBSESSION_USERGROUP_STATUS['MEMBER']) # changed groups? changed_groups = usersgroups_set & groups_already_known_set groups_description = db.get_all_groups_description(login_method) for group_name in changed_groups: users_already_in_group = db.get_users_in_group( groups_already_known[group_name]) users_already_in_group_set = set(users_already_in_group) users_in_group_set = usersgroups[group_name][1] # no more affiliation nomore_users = users_already_in_group_set - users_in_group_set for uid in nomore_users: db.delete_member(groups_already_known[group_name], uid) # new affiliation new_users = users_in_group_set - users_already_in_group_set for uid in new_users: db.insert_new_member(uid, groups_already_known[group_name], CFG_WEBSESSION_USERGROUP_STATUS['MEMBER']) # check description if groups_description[group_name] != usersgroups[group_name][0]: db.update_group_infos(groups_already_known[group_name], group_name, usersgroups[group_name][0], CFG_WEBSESSION_GROUP_JOIN_POLICY['VISIBLEEXTERNAL']) def group_name_valid_p(group_name): """Test if the group's name is valid.""" return nickname_valid_p(group_name) diff --git a/modules/websession/lib/websession_webinterface.py b/modules/websession/lib/websession_webinterface.py index 56745c776..799474296 100644 --- a/modules/websession/lib/websession_webinterface.py +++ b/modules/websession/lib/websession_webinterface.py @@ -1,1296 +1,1357 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Invenio ACCOUNT HANDLING""" __revision__ = "$Id$" __lastupdated__ = """$Date$""" import cgi from datetime import timedelta +import os from invenio.config import \ CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS, \ CFG_ACCESS_CONTROL_LEVEL_SITE, \ CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT, \ CFG_SITE_NAME, \ CFG_SITE_NAME_INTL, \ CFG_SITE_SUPPORT_EMAIL, \ CFG_SITE_SECURE_URL, \ CFG_SITE_URL, \ CFG_CERN_SITE, \ CFG_WEBSESSION_RESET_PASSWORD_EXPIRE_IN_DAYS from invenio import webuser from invenio.webpage import page from invenio import webaccount from invenio import webbasket from invenio import webalert from invenio.dbquery import run_sql from invenio.webmessage import account_new_mail -from invenio.access_control_engine import make_apache_message, make_list_apache_firerole, acc_authorize_action +from invenio.access_control_engine import acc_authorize_action from invenio.webinterface_handler import wash_urlargd, WebInterfaceDirectory +from invenio.webinterface_handler_config import HTTP_BAD_REQUEST from invenio.urlutils import redirect_to_url, make_canonical_urlargd from invenio import webgroup from invenio import webgroup_dblayer from invenio.messages import gettext_set_language, wash_language from invenio.mailutils import send_email from invenio.access_control_mailcookie import mail_cookie_retrieve_kind, \ mail_cookie_check_pw_reset, mail_cookie_delete_cookie, \ mail_cookie_create_pw_reset, mail_cookie_check_role, \ mail_cookie_check_mail_activation, InvenioWebAccessMailCookieError, \ InvenioWebAccessMailCookieDeletedError, mail_cookie_check_authorize_action from invenio.access_control_config import CFG_WEBACCESS_WARNING_MSGS, \ CFG_EXTERNAL_AUTH_USING_SSO, CFG_EXTERNAL_AUTH_LOGOUT_SSO, \ CFG_EXTERNAL_AUTHENTICATION import invenio.template websession_templates = invenio.template.load('websession') bibcatalog_templates = invenio.template.load('bibcatalog') class WebInterfaceYourAccountPages(WebInterfaceDirectory): _exports = ['', 'edit', 'change', 'lost', 'display', 'send_email', 'youradminactivities', 'access', - 'delete', 'logout', 'login', 'register', 'resetpassword'] + 'delete', 'logout', 'login', 'register', 'resetpassword', + 'robotlogin', 'robotlogout'] _force_https = True def index(self, req, form): redirect_to_url(req, '%s/youraccount/display' % CFG_SITE_SECURE_URL) def access(self, req, form): args = wash_urlargd(form, {'mailcookie' : (str, '')}) _ = gettext_set_language(args['ln']) title = _("Mail Cookie Service") try: kind = mail_cookie_retrieve_kind(args['mailcookie']) if kind == 'pw_reset': redirect_to_url(req, '%s/youraccount/resetpassword?k=%s&ln=%s' % (CFG_SITE_SECURE_URL, args['mailcookie'], args['ln'])) elif kind == 'role': uid = webuser.getUid(req) try: (role_name, expiration) = mail_cookie_check_role(args['mailcookie'], uid) except InvenioWebAccessMailCookieDeletedError: return page(title=_("Role authorization request"), req=req, body=_("This request for an authorization has already been authorized."), uid=webuser.getUid(req), navmenuid='youraccount', language=args['ln']) return page(title=title, body=webaccount.perform_back( _("You have successfully obtained an authorization as %(x_role)s! " "This authorization will last until %(x_expiration)s and until " "you close your browser if you are a guest user.") % {'x_role' : '%s' % role_name, 'x_expiration' : '%s' % expiration.strftime("%Y-%m-%d %H:%M:%S")}, '/youraccount/display?ln=%s' % args['ln'], _('login'), args['ln']), req=req, uid=webuser.getUid(req), language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') elif kind == 'mail_activation': try: email = mail_cookie_check_mail_activation(args['mailcookie']) if not email: raise StandardError webuser.confirm_email(email) body = "

    " + _("You have confirmed the validity of your email" " address!") + "

    " if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS == 1: body += "

    " + _("Please, wait for the administrator to " "enable your account.") + "

    " else: uid = webuser.update_Uid(req, email) body += "

    " + _("You can now go to %(x_url_open)syour account page%(x_url_close)s.") % {'x_url_open' : '' % args['ln'], 'x_url_close' : ''} + "

    " return page(title=_("Email address successfully activated"), body=body, req=req, language=args['ln'], uid=webuser.getUid(req), lastupdated=__lastupdated__, navmenuid='youraccount') except InvenioWebAccessMailCookieDeletedError, e: body = "

    " + _("You have already confirmed the validity of your email address!") + "

    " if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS == 1: body += "

    " + _("Please, wait for the administrator to " "enable your account.") + "

    " else: body += "

    " + _("You can now go to %(x_url_open)syour account page%(x_url_close)s.") % {'x_url_open' : '' % args['ln'], 'x_url_close' : ''} + "

    " return page(title=_("Email address successfully activated"), body=body, req=req, language=args['ln'], uid=webuser.getUid(req), lastupdated=__lastupdated__, navmenuid='youraccount') return webuser.page_not_authorized(req, "../youraccount/access", text=_("This request for confirmation of an email " "address is not valid or" " is expired."), navmenuid='youraccount') except InvenioWebAccessMailCookieError: return webuser.page_not_authorized(req, "../youraccount/access", text=_("This request for an authorization is not valid or" " is expired."), navmenuid='youraccount') def resetpassword(self, req, form): args = wash_urlargd(form, { 'k' : (str, ''), 'reset' : (int, 0), 'password' : (str, ''), 'password2' : (str, '') }) _ = gettext_set_language(args['ln']) title = _('Reset password') reset_key = args['k'] try: email = mail_cookie_check_pw_reset(reset_key) except InvenioWebAccessMailCookieDeletedError: return page(title=title, req=req, body=_("This request for resetting a password has already been used."), uid=webuser.getUid(req), navmenuid='youraccount', language=args['ln']) except InvenioWebAccessMailCookieError: return webuser.page_not_authorized(req, "../youraccount/access", text=_("This request for resetting a password is not valid or" " is expired."), navmenuid='youraccount') if email is None or CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 3: return webuser.page_not_authorized(req, "../youraccount/resetpassword", text=_("This request for resetting the password is not valid or" " is expired."), navmenuid='youraccount') if not args['reset']: return page(title=title, body=webaccount.perform_reset_password(args['ln'], email, reset_key), req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') elif args['password'] != args['password2']: msg = _('The two provided passwords aren\'t equal.') return page(title=title, body=webaccount.perform_reset_password(args['ln'], email, reset_key, msg), req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') run_sql('UPDATE user SET password=AES_ENCRYPT(email,%s) WHERE email=%s', (args['password'], email)) mail_cookie_delete_cookie(reset_key) return page(title=title, body=webaccount.perform_back( _("The password was successfully set! " "You can now proceed with the login."), '/youraccount/login?ln=%s' % args['ln'], _('login'), args['ln']), req=req, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') def display(self, req, form): args = wash_urlargd(form, {}) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(args['ln']) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../youraccount/display", navmenuid='youraccount') if webuser.isGuestUser(uid): return page(title=_("Your Account"), body=webaccount.perform_info(req, args['ln']), description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') username = webuser.get_nickname_or_email(uid) user_info = webuser.collect_user_info(req) bask = user_info['precached_usebaskets'] and webbasket.account_list_baskets(uid, ln=args['ln']) or '' aler = user_info['precached_usealerts'] and webalert.account_list_alerts(uid, ln=args['ln']) or '' sear = webalert.account_list_searches(uid, ln=args['ln']) msgs = user_info['precached_usemessages'] and account_new_mail(uid, ln=args['ln']) or '' grps = user_info['precached_usegroups'] and webgroup.account_group(uid, ln=args['ln']) or '' appr = user_info['precached_useapprove'] sbms = user_info['precached_viewsubmissions'] loan = '' admn = webaccount.perform_youradminactivities(user_info, args['ln']) return page(title=_("Your Account"), body=webaccount.perform_display_account(req, username, bask, aler, sear, msgs, loan, grps, sbms, appr, admn, args['ln']), description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') def edit(self, req, form): args = wash_urlargd(form, {"verbose" : (int, 0)}) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(args['ln']) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../youraccount/edit", navmenuid='youraccount') if webuser.isGuestUser(uid): return webuser.page_not_authorized(req, "../youraccount/edit", text=_("This functionality is forbidden to guest users."), navmenuid='youraccount') body = '' user_info = webuser.collect_user_info(req) if args['verbose'] == 9: keys = user_info.keys() keys.sort() for key in keys: body += "%s:%s
    " % (key, user_info[key]) #check if the user should see bibcatalog user name / passwd in the settings can_config_bibcatalog = (acc_authorize_action(user_info, 'runbibedit')[0] == 0) return page(title= _("Your Settings"), body=body+webaccount.perform_set(webuser.get_email(uid), args['ln'], can_config_bibcatalog, verbose=args['verbose']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description=_("%s Personalize, Your Settings") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') def change(self, req, form): args = wash_urlargd(form, { 'nickname': (str, None), 'email': (str, None), 'old_password': (str, None), 'password': (str, None), 'password2': (str, None), 'login_method': (str, ""), 'group_records' : (int, None), 'latestbox' : (int, None), 'helpbox' : (int, None), 'lang' : (str, None), 'bibcatalog_username' : (str, None), 'bibcatalog_password' : (str, None), }) ## Wash arguments: args['login_method'] = wash_login_method(args['login_method']) if args['email']: args['email'] = args['email'].lower() ## Load the right message language: _ = gettext_set_language(args['ln']) ## Identify user and load old preferences: uid = webuser.getUid(req) prefs = webuser.get_user_preferences(uid) ## Check rights: if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../youraccount/change", navmenuid='youraccount') # FIXME: the branching below is far from optimal. Should be # based on the submitted form name ids, to know precisely on # which form the user clicked. Not on the passed values, as # is the case now. The function body is too big and in bad # need of refactoring anyway. ## Will hold the output messages: mess = '' ## Change login method if needed: if args['login_method'] and CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS < 4 \ - and args['login_method'] in CFG_EXTERNAL_AUTHENTICATION.keys(): + and args['login_method'] in CFG_EXTERNAL_AUTHENTICATION: title = _("Settings edited") act = "/youraccount/display?ln=%s" % args['ln'] linkname = _("Show account") if prefs['login_method'] != args['login_method']: if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 4: mess += '

    ' + _("Unable to change login method.") - elif not CFG_EXTERNAL_AUTHENTICATION[args['login_method']][0]: + elif not CFG_EXTERNAL_AUTHENTICATION[args['login_method']]: # Switching to internal authentication: we drop any external datas p_email = webuser.get_email(uid) webuser.drop_external_settings(uid) webgroup_dblayer.drop_external_groups(uid) prefs['login_method'] = args['login_method'] webuser.set_user_preferences(uid, prefs) mess += "

    " + _("Switched to internal login method.") + " " mess += _("Please note that if this is the first time that you are using this account " "with the internal login method then the system has set for you " "a randomly generated password. Please click the " "following button to obtain a password reset request " "link sent to you via email:") + '

    ' mess += """

    """ % (p_email, _("Send Password")) else: query = """SELECT email FROM user WHERE id = %i""" res = run_sql(query % uid) if res: email = res[0][0] else: email = None if not email: mess += '

    ' + _("Unable to switch to external login method %s, because your email address is unknown.") % cgi.escape(args['login_method']) else: try: - if not CFG_EXTERNAL_AUTHENTICATION[args['login_method']][0].user_exists(email): + if not CFG_EXTERNAL_AUTHENTICATION[args['login_method']].user_exists(email): mess += '

    ' + _("Unable to switch to external login method %s, because your email address is unknown to the external login system.") % cgi.escape(args['login_method']) else: prefs['login_method'] = args['login_method'] webuser.set_user_preferences(uid, prefs) mess += '

    ' + _("Login method successfully selected.") except AttributeError: mess += '

    ' + _("The external login method %s does not support email address based logins. Please contact the site administrators.") % cgi.escape(args['login_method']) ## Change email or nickname: if args['email'] or args['nickname']: uid2 = webuser.emailUnique(args['email']) uid_with_the_same_nickname = webuser.nicknameUnique(args['nickname']) if (CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 2 or (CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS <= 1 and \ webuser.email_valid_p(args['email']))) \ and (args['nickname'] is None or webuser.nickname_valid_p(args['nickname'])) \ and uid2 != -1 and (uid2 == uid or uid2 == 0) \ and uid_with_the_same_nickname != -1 and (uid_with_the_same_nickname == uid or uid_with_the_same_nickname == 0): if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS < 3: change = webuser.updateDataUser(uid, args['email'], args['nickname']) else: return webuser.page_not_authorized(req, "../youraccount/change", navmenuid='youraccount') if change: mess += '

    ' + _("Settings successfully edited.") mess += '

    ' + _("Note that if you have changed your email address, " "you will have to %(x_url_open)sreset your password%(x_url_close)s anew.") % \ {'x_url_open': '' % (CFG_SITE_SECURE_URL + '/youraccount/lost?ln=%s' % args['ln']), 'x_url_close': ''} act = "/youraccount/display?ln=%s" % args['ln'] linkname = _("Show account") title = _("Settings edited") elif args['nickname'] is not None and not webuser.nickname_valid_p(args['nickname']): mess += '

    ' + _("Desired nickname %s is invalid.") % cgi.escape(args['nickname']) mess += " " + _("Please try again.") act = "/youraccount/edit?ln=%s" % args['ln'] linkname = _("Edit settings") title = _("Editing settings failed") elif not webuser.email_valid_p(args['email']): mess += '

    ' + _("Supplied email address %s is invalid.") % cgi.escape(args['email']) mess += " " + _("Please try again.") act = "/youraccount/edit?ln=%s" % args['ln'] linkname = _("Edit settings") title = _("Editing settings failed") elif uid2 == -1 or uid2 != uid and not uid2 == 0: mess += '

    ' + _("Supplied email address %s already exists in the database.") % cgi.escape(args['email']) mess += " " + websession_templates.tmpl_lost_your_password_teaser(args['ln']) mess += " " + _("Or please try again.") act = "/youraccount/edit?ln=%s" % args['ln'] linkname = _("Edit settings") title = _("Editing settings failed") elif uid_with_the_same_nickname == -1 or uid_with_the_same_nickname != uid and not uid_with_the_same_nickname == 0: mess += '

    ' + _("Desired nickname %s is already in use.") % cgi.escape(args['nickname']) mess += " " + _("Please try again.") act = "/youraccount/edit?ln=%s" % args['ln'] linkname = _("Edit settings") title = _("Editing settings failed") ## Change passwords: if args['old_password'] or args['password'] or args['password2']: if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 3: mess += '

    ' + _("Users cannot edit passwords on this site.") else: res = run_sql("SELECT id FROM user " "WHERE AES_ENCRYPT(email,%s)=password AND id=%s", (args['old_password'], uid)) if res: if args['password'] == args['password2']: webuser.updatePasswordUser(uid, args['password']) mess += '

    ' + _("Password successfully edited.") act = "/youraccount/display?ln=%s" % args['ln'] linkname = _("Show account") title = _("Password edited") else: mess += '

    ' + _("Both passwords must match.") mess += " " + _("Please try again.") act = "/youraccount/edit?ln=%s" % args['ln'] linkname = _("Edit settings") title = _("Editing password failed") else: mess += '

    ' + _("Wrong old password inserted.") mess += " " + _("Please try again.") act = "/youraccount/edit?ln=%s" % args['ln'] linkname = _("Edit settings") title = _("Editing password failed") ## Change search-related settings: if args['group_records']: prefs = webuser.get_user_preferences(uid) prefs['websearch_group_records'] = args['group_records'] prefs['websearch_latestbox'] = args['latestbox'] prefs['websearch_helpbox'] = args['helpbox'] webuser.set_user_preferences(uid, prefs) title = _("Settings edited") act = "/youraccount/display?ln=%s" % args['ln'] linkname = _("Show account") mess += '

    ' + _("User settings saved correctly.") ## Change language-related settings: if args['lang']: lang = wash_language(args['lang']) prefs = webuser.get_user_preferences(uid) prefs['language'] = lang args['ln'] = lang _ = gettext_set_language(lang) webuser.set_user_preferences(uid, prefs) title = _("Settings edited") act = "/youraccount/display?ln=%s" % args['ln'] linkname = _("Show account") mess += '

    ' + _("User settings saved correctly.") ## Edit cataloging-related settings: if args['bibcatalog_username'] or args['bibcatalog_password']: act = "/youraccount/display?ln=%s" % args['ln'] linkname = _("Show account") if ((len(args['bibcatalog_username']) == 0) or (len(args['bibcatalog_password']) == 0)): title = _("Editing bibcatalog authorization failed") mess += '

    ' + _("Empty username or password") else: title = _("Settings edited") prefs['bibcatalog_username'] = args['bibcatalog_username'] prefs['bibcatalog_password'] = args['bibcatalog_password'] webuser.set_user_preferences(uid, prefs) mess += '

    ' + _("User settings saved correctly.") if not mess: mess = _("Unable to update settings.") if not act: act = "/youraccount/edit?ln=%s" % args['ln'] if not linkname: linkname = _("Edit settings") if not title: title = _("Editing settings failed") ## Finally, output the results: return page(title=title, body=webaccount.perform_back(mess, act, linkname, args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') def lost(self, req, form): args = wash_urlargd(form, {}) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(args['ln']) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../youraccount/lost", navmenuid='youraccount') return page(title=_("Lost your password?"), body=webaccount.perform_lost(args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') def send_email(self, req, form): # set all the declared query fields as local variables args = wash_urlargd(form, {'p_email': (str, None)}) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(args['ln']) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../youraccount/send_email", navmenuid='youraccount') user_prefs = webuser.get_user_preferences(webuser.emailUnique(args['p_email'])) if user_prefs: - if CFG_EXTERNAL_AUTHENTICATION.has_key(user_prefs['login_method']) and \ - CFG_EXTERNAL_AUTHENTICATION[user_prefs['login_method']][0] is not None: + if user_prefs['login_method'] in CFG_EXTERNAL_AUTHENTICATION and \ + CFG_EXTERNAL_AUTHENTICATION[user_prefs['login_method']] is not None: eMsg = _("Cannot send password reset request since you are using external authentication system.") return page(title=_("Your Account"), body=webaccount.perform_emailMessage(eMsg, args['ln']), description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME)), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') try: reset_key = mail_cookie_create_pw_reset(args['p_email'], cookie_timeout=timedelta(days=CFG_WEBSESSION_RESET_PASSWORD_EXPIRE_IN_DAYS)) except InvenioWebAccessMailCookieError: reset_key = None if reset_key is None: eMsg = _("The entered email address does not exist in the database.") return page(title=_("Your Account"), body=webaccount.perform_emailMessage(eMsg, args['ln']), description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') ip_address = req.remote_host or req.remote_ip if not send_email(CFG_SITE_SUPPORT_EMAIL, args['p_email'], "%s %s" % (_("Password reset request for"), CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME)), websession_templates.tmpl_account_reset_password_email_body( args['p_email'],reset_key, ip_address, args['ln'])): eMsg = _("The entered email address is incorrect, please check that it is written correctly (e.g. johndoe@example.com).") return page(title=_("Incorrect email address"), body=webaccount.perform_emailMessage(eMsg, args['ln']), description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') return page(title=_("Reset password link sent"), body=webaccount.perform_emailSent(args['p_email'], args['ln']), description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') def youradminactivities(self, req, form): args = wash_urlargd(form, {}) uid = webuser.getUid(req) user_info = webuser.collect_user_info(req) # load the right message language _ = gettext_set_language(args['ln']) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../youraccount/youradminactivities", navmenuid='admin') return page(title=_("Your Administrative Activities"), body=webaccount.perform_youradminactivities(user_info, args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='admin') def delete(self, req, form): args = wash_urlargd(form, {}) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(args['ln']) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../youraccount/delete", navmenuid='youraccount') return page(title=_("Delete Account"), body=webaccount.perform_delete(args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') def logout(self, req, form): args = wash_urlargd(form, {}) uid = webuser.logoutUser(req) # load the right message language _ = gettext_set_language(args['ln']) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../youraccount/logout", navmenuid='youraccount') if CFG_EXTERNAL_AUTH_USING_SSO: return redirect_to_url(req, CFG_EXTERNAL_AUTH_LOGOUT_SSO) return page(title=_("Logout"), body=webaccount.perform_logout(req, args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords=_("%s, personalize") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') + def robotlogout(self, req, form): + """ + Implement logout method for external service providers. + """ + webuser.logoutUser(req) + redirect_to_url(req, "%s/img/pix.png" % CFG_SITE_URL) + + def robotlogin(self, req, form): + """ + Implement authentication method for external service providers. + """ + from invenio.external_authentication import InvenioWebAccessExternalAuthError + args = wash_urlargd(form, { + 'login_method': (str, None), + 'remember_me' : (str, ''), + 'referer': (str, ''), + 'p_un': (str, ''), + 'p_pw': (str, '') + }) + # sanity checks: + args['login_method'] = wash_login_method(args['login_method']) + args['remember_me'] = args['remember_me'] != '' + locals().update(args) + if CFG_ACCESS_CONTROL_LEVEL_SITE > 0: + return webuser.page_not_authorized(req, "../youraccount/login?ln=%s" % args['ln'], + navmenuid='youraccount') + uid = webuser.getUid(req) + + # load the right message language + _ = gettext_set_language(args['ln']) + + try: + (iden, args['p_un'], args['p_pw'], msgcode) = webuser.loginUser(req, args['p_un'], args['p_pw'], args['login_method']) + except InvenioWebAccessExternalAuthError, err: + return page("Error", body=str(err)) + if len(iden)>0: + uid = webuser.update_Uid(req, args['p_un'], args['remember_me']) + uid2 = webuser.getUid(req) + if uid2 == -1: + webuser.logoutUser(req) + return webuser.page_not_authorized(req, "../youraccount/login?ln=%s" % args['ln'], uid=uid, + navmenuid='youraccount') + + # login successful! + if args['referer']: + redirect_to_url(req, args['referer']) + else: + return self.display(req, form) + else: + mess = CFG_WEBACCESS_WARNING_MSGS[msgcode] % cgi.escape(args['login_method']) + if msgcode == 14: + if webuser.username_exists_p(args['p_un']): + mess = CFG_WEBACCESS_WARNING_MSGS[15] % cgi.escape(args['login_method']) + act = '/youraccount/login%s' % make_canonical_urlargd({'ln' : args['ln'], 'referer' : args['referer']}, {}) + return page(title=_("Login"), + body=webaccount.perform_back(mess, act, _("login"), args['ln']), + navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", + description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), + keywords="%s , personalize" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), + uid=uid, + req=req, + secure_page_p = 1, + language=args['ln'], + lastupdated=__lastupdated__, + navmenuid='youraccount') + + + def login(self, req, form): args = wash_urlargd(form, { 'p_un': (str, None), 'p_pw': (str, None), 'login_method': (str, None), 'action': (str, ''), 'remember_me' : (str, ''), 'referer': (str, '')}) # sanity checks: args['login_method'] = wash_login_method(args['login_method']) if args['p_un']: args['p_un'] = args['p_un'].strip() args['remember_me'] = args['remember_me'] != '' locals().update(args) if CFG_ACCESS_CONTROL_LEVEL_SITE > 0: return webuser.page_not_authorized(req, "../youraccount/login?ln=%s" % args['ln'], navmenuid='youraccount') uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(args['ln']) - apache_msg = "" if args['action']: cookie = args['action'] try: action, arguments = mail_cookie_check_authorize_action(cookie) - apache_msg = make_apache_message(action, arguments, args['referer']) - - # FIXME: Temporary Hack to help CDS current migration - if CFG_CERN_SITE: - roles = make_list_apache_firerole(action, arguments) - if len(roles) == 1: - # There's only one role enabled to see this collection - # Let's redirect to log to it! - return redirect_to_url(req, '%s/%s' % (CFG_SITE_SECURE_URL, make_canonical_urlargd({'realm' : roles[0][0], 'referer' : args['referer']}, {}))) except InvenioWebAccessMailCookieError: pass if not CFG_EXTERNAL_AUTH_USING_SSO: if args['p_un'] is None or not args['login_method']: return page(title=_("Login"), - body=webaccount.create_login_page_box(args['referer'], apache_msg, args['ln']), + body=webaccount.create_login_page_box(args['referer'], args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords="%s , personalize" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') (iden, args['p_un'], args['p_pw'], msgcode) = webuser.loginUser(req, args['p_un'], args['p_pw'], args['login_method']) else: # Fake parameters for p_un & p_pw because SSO takes them from the environment (iden, args['p_un'], args['p_pw'], msgcode) = webuser.loginUser(req, '', '', CFG_EXTERNAL_AUTH_USING_SSO) args['remember_me'] = False if len(iden)>0: uid = webuser.update_Uid(req, args['p_un'], args['remember_me']) uid2 = webuser.getUid(req) if uid2 == -1: webuser.logoutUser(req) return webuser.page_not_authorized(req, "../youraccount/login?ln=%s" % args['ln'], uid=uid, navmenuid='youraccount') # login successful! if args['referer']: redirect_to_url(req, args['referer']) else: return self.display(req, form) else: mess = CFG_WEBACCESS_WARNING_MSGS[msgcode] % cgi.escape(args['login_method']) if msgcode == 14: if webuser.username_exists_p(args['p_un']): mess = CFG_WEBACCESS_WARNING_MSGS[15] % cgi.escape(args['login_method']) act = '/youraccount/login%s' % make_canonical_urlargd({'ln' : args['ln'], 'referer' : args['referer']}, {}) return page(title=_("Login"), body=webaccount.perform_back(mess, act, _("login"), args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description="%s Personalize, Main page" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords="%s , personalize" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') def register(self, req, form): args = wash_urlargd(form, { 'p_nickname': (str, None), 'p_email': (str, None), 'p_pw': (str, None), 'p_pw2': (str, None), 'action': (str, "login"), 'referer': (str, "")}) if CFG_ACCESS_CONTROL_LEVEL_SITE > 0: return webuser.page_not_authorized(req, "../youraccount/register?ln=%s" % args['ln'], navmenuid='youraccount') uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(args['ln']) if args['p_nickname'] is None or args['p_email'] is None: return page(title=_("Register"), body=webaccount.create_register_page_box(args['referer'], args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description=_("%s Personalize, Main page") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords="%s , personalize" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') mess = "" act = "" if args['p_pw'] == args['p_pw2']: ruid = webuser.registerUser(req, args['p_email'], args['p_pw'], args['p_nickname'], ln=args['ln']) else: ruid = -2 if ruid == 0: mess = _("Your account has been successfully created.") title = _("Account created") if CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT == 1: mess += " " + _("In order to confirm its validity, an email message containing an account activation key has been sent to the given email address.") mess += " " + _("Please follow instructions presented there in order to complete the account registration process.") if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 1: mess += " " + _("A second email will be sent when the account has been activated and can be used.") elif CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT != 1: uid = webuser.update_Uid(req, args['p_email']) mess += " " + _("You can now access your %(x_url_open)saccount%(x_url_close)s.") %\ {'x_url_open': '', 'x_url_close': ''} elif ruid == -2: mess = _("Both passwords must match.") mess += " " + _("Please try again.") act = "/youraccount/register?ln=%s" % args['ln'] title = _("Registration failure") elif ruid == 1: mess = _("Supplied email address %s is invalid.") % cgi.escape(args['p_email']) mess += " " + _("Please try again.") act = "/youraccount/register?ln=%s" % args['ln'] title = _("Registration failure") elif ruid == 2: mess = _("Desired nickname %s is invalid.") % cgi.escape(args['p_nickname']) mess += " " + _("Please try again.") act = "/youraccount/register?ln=%s" % args['ln'] title = _("Registration failure") elif ruid == 3: mess = _("Supplied email address %s already exists in the database.") % cgi.escape(args['p_email']) mess += " " + websession_templates.tmpl_lost_your_password_teaser(args['ln']) mess += " " + _("Or please try again.") act = "/youraccount/register?ln=%s" % args['ln'] title = _("Registration failure") elif ruid == 4: mess = _("Desired nickname %s already exists in the database.") % cgi.escape(args['p_nickname']) mess += " " + _("Please try again.") act = "/youraccount/register?ln=%s" % args['ln'] title = _("Registration failure") elif ruid == 5: mess = _("Users cannot register themselves, only admin can register them.") act = "/youraccount/register?ln=%s" % args['ln'] title = _("Registration failure") elif ruid == 6: mess = _("The site is having troubles in sending you an email for confirming your email address.") + _("The error has been logged and will be taken in consideration as soon as possible.") act = "/youraccount/register?ln=%s" % args['ln'] title = _("Registration failure") else: # this should never happen mess = _("Internal Error") act = "/youraccount/register?ln=%s" % args['ln'] title = _("Registration failure") return page(title=title, body=webaccount.perform_back(mess,act, _("register"), args['ln']), navtrail="""""" % (CFG_SITE_SECURE_URL, args['ln']) + _("Your Account") + """""", description=_("%s Personalize, Main page") % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), keywords="%s , personalize" % CFG_SITE_NAME_INTL.get(args['ln'], CFG_SITE_NAME), uid=uid, req=req, secure_page_p = 1, language=args['ln'], lastupdated=__lastupdated__, navmenuid='youraccount') class WebInterfaceYourTicketsPages(WebInterfaceDirectory): #support for /yourtickets url _exports = ['', 'display'] def __call__(self, req, form): #if there is no trailing slash self.index(req, form) def index(self, req, form): #take all the parameters.. unparsed_uri = req.unparsed_uri qstr = "" if unparsed_uri.count('?') > 0: dummy, qstr = unparsed_uri.split('?') qstr = '?'+qstr redirect_to_url(req, '/yourtickets/display'+qstr) def display(self, req, form): #show tickets for this user argd = wash_urlargd(form, {'ln': (str, ''), 'start': (int, 1) }) uid = webuser.getUid(req) ln = argd['ln'] start = argd['start'] _ = gettext_set_language(ln) body = bibcatalog_templates.tmpl_your_tickets(uid, ln, start) return page(title=_("Your tickets"), body=body, navtrail="""""" % (CFG_SITE_SECURE_URL, argd['ln']) + _("Your Account") + """""", uid=uid, req=req, language=argd['ln'], lastupdated=__lastupdated__) class WebInterfaceYourGroupsPages(WebInterfaceDirectory): _exports = ['', 'display', 'create', 'join', 'leave', 'edit', 'members'] def index(self, req, form): redirect_to_url(req, '/yourgroups/display') def display(self, req, form): """ Displays groups the user is admin of and the groups the user is member of(but not admin) @param ln: language @return: the page for all the groups """ argd = wash_urlargd(form, {}) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(argd['ln']) if uid == -1 or webuser.isGuestUser(uid) or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../yourgroups/display", navmenuid='yourgroups') user_info = webuser.collect_user_info(req) if not user_info['precached_usegroups']: return webuser.page_not_authorized(req, "../", \ text = _("You are not authorized to use groups.")) (body, errors, warnings) = webgroup.perform_request_groups_display(uid=uid, ln=argd['ln']) return page(title = _("Your Groups"), body = body, navtrail = webgroup.get_navtrail(argd['ln']), uid = uid, req = req, language = argd['ln'], lastupdated = __lastupdated__, errors = errors, warnings = warnings, navmenuid = 'yourgroups') def create(self, req, form): """create(): interface for creating a new group @param group_name: : name of the new webgroup.Must be filled @param group_description: : description of the new webgroup.(optionnal) @param join_policy: : join policy of the new webgroup.Must be chosen @param *button: which button was pressed @param ln: language @return: the compose page Create group """ argd = wash_urlargd(form, {'group_name': (str, ""), 'group_description': (str, ""), 'join_policy': (str, ""), 'create_button':(str, ""), 'cancel':(str, "") }) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(argd['ln']) if uid == -1 or webuser.isGuestUser(uid) or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../yourgroups/create", navmenuid='yourgroups') user_info = webuser.collect_user_info(req) if not user_info['precached_usegroups']: return webuser.page_not_authorized(req, "../", \ text = _("You are not authorized to use groups.")) if argd['cancel']: url = CFG_SITE_URL + '/yourgroups/display?ln=%s' url %= argd['ln'] redirect_to_url(req, url) if argd['create_button'] : (body, errors, warnings)= webgroup.perform_request_create_group(uid=uid, group_name=argd['group_name'], group_description=argd['group_description'], join_policy=argd['join_policy'], ln = argd['ln']) else: (body, errors, warnings) = webgroup.perform_request_input_create_group(group_name=argd['group_name'], group_description=argd['group_description'], join_policy=argd['join_policy'], ln=argd['ln']) title = _("Create new group") return page(title = title, body = body, navtrail = webgroup.get_navtrail(argd['ln'], title), uid = uid, req = req, language = argd['ln'], lastupdated = __lastupdated__, errors = errors, warnings = warnings, navmenuid = 'yourgroups') def join(self, req, form): """join(): interface for joining a new group @param grpID: : list of the group the user wants to become a member. The user must select only one group. @param group_name: : will search for groups matching group_name @param *button: which button was pressed @param ln: language @return: the compose page Join group """ argd = wash_urlargd(form, {'grpID':(list, []), 'group_name':(str, ""), 'find_button':(str, ""), 'join_button':(str, ""), 'cancel':(str, "") }) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(argd['ln']) if uid == -1 or webuser.isGuestUser(uid) or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../yourgroups/join", navmenuid='yourgroups') user_info = webuser.collect_user_info(req) if not user_info['precached_usegroups']: return webuser.page_not_authorized(req, "../", \ text = _("You are not authorized to use groups.")) if argd['cancel']: url = CFG_SITE_URL + '/yourgroups/display?ln=%s' url %= argd['ln'] redirect_to_url(req, url) if argd['join_button']: search = 0 if argd['group_name']: search = 1 (body, errors, warnings) = webgroup.perform_request_join_group(uid, argd['grpID'], argd['group_name'], search, argd['ln']) else: search = 0 if argd['find_button']: search = 1 (body, errors, warnings) = webgroup.perform_request_input_join_group(uid, argd['group_name'], search, ln=argd['ln']) title = _("Join New Group") return page(title = title, body = body, navtrail = webgroup.get_navtrail(argd['ln'], title), uid = uid, req = req, language = argd['ln'], lastupdated = __lastupdated__, errors = errors, warnings = warnings, navmenuid = 'yourgroups') def leave(self, req, form): """leave(): interface for leaving a group @param grpID: : group the user wants to leave. @param group_name: : name of the group the user wants to leave @param *button: which button was pressed @param confirmed: : the user is first asked to confirm @param ln: language @return: the compose page Leave group """ argd = wash_urlargd(form, {'grpID':(int, 0), 'group_name':(str, ""), 'leave_button':(str, ""), 'cancel':(str, ""), 'confirmed': (int, 0) }) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(argd['ln']) if uid == -1 or webuser.isGuestUser(uid) or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../yourgroups/leave", navmenuid='yourgroups') user_info = webuser.collect_user_info(req) if not user_info['precached_usegroups']: return webuser.page_not_authorized(req, "../", \ text = _("You are not authorized to use groups.")) if argd['cancel']: url = CFG_SITE_URL + '/yourgroups/display?ln=%s' url %= argd['ln'] redirect_to_url(req, url) if argd['leave_button']: (body, errors, warnings) = webgroup.perform_request_leave_group(uid, argd['grpID'], argd['confirmed'], argd['ln']) else: (body, errors, warnings) = webgroup.perform_request_input_leave_group(uid=uid, ln=argd['ln']) title = _("Leave Group") return page(title = title, body = body, navtrail = webgroup.get_navtrail(argd['ln'], title), uid = uid, req = req, language = argd['ln'], lastupdated = __lastupdated__, errors = errors, warnings = warnings, navmenuid = 'yourgroups') def edit(self, req, form): """edit(): interface for editing group @param grpID: : group ID @param group_name: : name of the new webgroup.Must be filled @param group_description: : description of the new webgroup.(optionnal) @param join_policy: : join policy of the new webgroup.Must be chosen @param update: button update group pressed @param delete: button delete group pressed @param cancel: button cancel pressed @param confirmed: : the user is first asked to confirm before deleting @param ln: language @return: the main page displaying all the groups """ argd = wash_urlargd(form, {'grpID': (int, 0), 'update': (str, ""), 'cancel': (str, ""), 'delete': (str, ""), 'group_name': (str, ""), 'group_description': (str, ""), 'join_policy': (str, ""), 'confirmed': (int, 0) }) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(argd['ln']) if uid == -1 or webuser.isGuestUser(uid) or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../yourgroups/display", navmenuid='yourgroups') user_info = webuser.collect_user_info(req) if not user_info['precached_usegroups']: return webuser.page_not_authorized(req, "../", \ text = _("You are not authorized to use groups.")) if argd['cancel']: url = CFG_SITE_URL + '/yourgroups/display?ln=%s' url %= argd['ln'] redirect_to_url(req, url) elif argd['delete']: (body, errors, warnings) = webgroup.perform_request_delete_group(uid=uid, grpID=argd['grpID'], confirmed=argd['confirmed']) elif argd['update']: (body, errors, warnings) = webgroup.perform_request_update_group(uid= uid, grpID=argd['grpID'], group_name=argd['group_name'], group_description=argd['group_description'], join_policy=argd['join_policy'], ln=argd['ln']) else : (body, errors, warnings)= webgroup.perform_request_edit_group(uid=uid, grpID=argd['grpID'], ln=argd['ln']) title = _("Edit Group") return page(title = title, body = body, navtrail = webgroup.get_navtrail(argd['ln'], title), uid = uid, req = req, language = argd['ln'], lastupdated = __lastupdated__, errors = errors, warnings = warnings, navmenuid = 'yourgroups') def members(self, req, form): """member(): interface for managing members of a group @param grpID: : group ID @param add_member: button add_member pressed @param remove_member: button remove_member pressed @param reject_member: button reject__member pressed @param delete: button delete group pressed @param member_id: : ID of the existing member selected @param pending_member_id: : ID of the pending member selected @param cancel: button cancel pressed @param info: : info about last user action @param ln: language @return: the same page with data updated """ argd = wash_urlargd(form, {'grpID': (int, 0), 'cancel': (str, ""), 'add_member': (str, ""), 'remove_member': (str, ""), 'reject_member': (str, ""), 'member_id': (int, 0), 'pending_member_id': (int, 0) }) uid = webuser.getUid(req) # load the right message language _ = gettext_set_language(argd['ln']) if uid == -1 or webuser.isGuestUser(uid) or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return webuser.page_not_authorized(req, "../yourgroups/display", navmenuid='yourgroups') user_info = webuser.collect_user_info(req) if not user_info['precached_usegroups']: return webuser.page_not_authorized(req, "../", \ text = _("You are not authorized to use groups.")) if argd['cancel']: url = CFG_SITE_URL + '/yourgroups/display?ln=%s' url %= argd['ln'] redirect_to_url(req, url) if argd['remove_member']: (body, errors, warnings) = webgroup.perform_request_remove_member(uid=uid, grpID=argd['grpID'], member_id=argd['member_id'], ln=argd['ln']) elif argd['reject_member']: (body, errors, warnings) = webgroup.perform_request_reject_member(uid=uid, grpID=argd['grpID'], user_id=argd['pending_member_id'], ln=argd['ln']) elif argd['add_member']: (body, errors, warnings) = webgroup.perform_request_add_member(uid=uid, grpID=argd['grpID'], user_id=argd['pending_member_id'], ln=argd['ln']) else: (body, errors, warnings)= webgroup.perform_request_manage_member(uid=uid, grpID=argd['grpID'], ln=argd['ln']) title = _("Edit group members") return page(title = title, body = body, navtrail = webgroup.get_navtrail(argd['ln'], title), uid = uid, req = req, language = argd['ln'], lastupdated = __lastupdated__, errors = errors, warnings = warnings, navmenuid = 'yourgroups') def wash_login_method(login_method): """ Wash the login_method parameter that came from the web input form. @param login_method: Wanted login_method value as it came from the web input form. @type login_method: string @return: Washed version of login_method. If the login_method value is valid, then return it. If it is not valid, then return `Local' (the default login method). @rtype: string @warning: Beware, 'Local' is hardcoded here! """ - if login_method in CFG_EXTERNAL_AUTHENTICATION.keys(): + if login_method in CFG_EXTERNAL_AUTHENTICATION: return login_method else: return 'Local' diff --git a/modules/websession/lib/webuser.py b/modules/websession/lib/webuser.py index b8c427cb6..2320d60f5 100644 --- a/modules/websession/lib/webuser.py +++ b/modules/websession/lib/webuser.py @@ -1,1298 +1,1187 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ This file implements all methods necessary for working with users and sessions in Invenio. Contains methods for logging/registration when a user log/register into the system, checking if it is a guest user or not. At the same time this presents all the stuff it could need with sessions managements, working with websession. It also contains Apache-related user authentication stuff. """ __revision__ = "$Id$" from invenio import webinterface_handler_config as apache import cgi import urllib import urlparse from socket import gaierror import os import crypt import socket import smtplib import re import random import datetime import base64 from invenio.config import \ CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS, \ CFG_ACCESS_CONTROL_LEVEL_GUESTS, \ CFG_ACCESS_CONTROL_LEVEL_SITE, \ CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN, \ CFG_ACCESS_CONTROL_NOTIFY_ADMIN_ABOUT_NEW_ACCOUNTS, \ CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT, \ CFG_APACHE_GROUP_FILE, \ CFG_APACHE_PASSWORD_FILE, \ CFG_SITE_ADMIN_EMAIL, \ CFG_SITE_LANG, \ CFG_SITE_NAME, \ CFG_SITE_NAME_INTL, \ CFG_SITE_SUPPORT_EMAIL, \ CFG_SITE_SECURE_URL, \ CFG_TMPDIR, \ CFG_SITE_URL, \ CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS, \ CFG_CERN_SITE, \ CFG_WEBSEARCH_PERMITTED_RESTRICTED_COLLECTIONS_LEVEL try: from invenio.session import get_session except ImportError: pass from invenio.dbquery import run_sql, OperationalError, \ serialize_via_marshal, deserialize_via_marshal from invenio.access_control_admin import acc_get_role_id, acc_get_action_roles, acc_get_action_id, acc_is_user_in_role, acc_find_possible_activities from invenio.access_control_mailcookie import mail_cookie_create_mail_activation from invenio.access_control_firerole import acc_firerole_check_user, load_role_definition from invenio.access_control_config import SUPERADMINROLE, CFG_EXTERNAL_AUTH_USING_SSO from invenio.messages import gettext_set_language, wash_languages, wash_language from invenio.mailutils import send_email from invenio.errorlib import register_exception from invenio.webgroup_dblayer import get_groups from invenio.external_authentication import InvenioWebAccessExternalAuthError from invenio.access_control_config import CFG_EXTERNAL_AUTHENTICATION, \ - CFG_WEBACCESS_MSGS, CFG_WEBACCESS_WARNING_MSGS + CFG_WEBACCESS_MSGS, CFG_WEBACCESS_WARNING_MSGS, CFG_EXTERNAL_AUTH_DEFAULT import invenio.template tmpl = invenio.template.load('websession') re_invalid_nickname = re.compile(""".*[,'@]+.*""") # pylint: disable=C0301 def createGuestUser(): """Create a guest user , insert into user null values in all fields createGuestUser() -> GuestUserID """ if CFG_ACCESS_CONTROL_LEVEL_GUESTS == 0: try: return run_sql("insert into user (email, note) values ('', '1')") except OperationalError: return None else: try: return run_sql("insert into user (email, note) values ('', '0')") except OperationalError: return None def page_not_authorized(req, referer='', uid='', text='', navtrail='', ln=CFG_SITE_LANG, navmenuid=""): """Show error message when user is not authorized to do something. @param referer: in case the displayed message propose a login link, this is the url to return to after logging in. If not specified it is guessed from req. @param uid: the uid of the user. If not specified it is guessed from req. @param text: the message to be displayed. If not specified it will be guessed from the context. """ from invenio.webpage import page _ = gettext_set_language(ln) if not referer: referer = req.unparsed_uri if not CFG_ACCESS_CONTROL_LEVEL_SITE: title = CFG_WEBACCESS_MSGS[5] if not uid: uid = getUid(req) try: res = run_sql("SELECT email FROM user WHERE id=%s AND note=1" % uid) if res and res[0][0]: if text: body = text else: body = "%s %s" % (CFG_WEBACCESS_WARNING_MSGS[9] % cgi.escape(res[0][0]), ("%s %s" % (CFG_WEBACCESS_MSGS[0] % urllib.quote(referer), CFG_WEBACCESS_MSGS[1]))) else: if text: body = text else: if CFG_ACCESS_CONTROL_LEVEL_GUESTS == 1: body = CFG_WEBACCESS_MSGS[3] else: body = CFG_WEBACCESS_WARNING_MSGS[4] + CFG_WEBACCESS_MSGS[2] except OperationalError, e: body = _("Database problem") + ': ' + str(e) elif CFG_ACCESS_CONTROL_LEVEL_SITE == 1: title = CFG_WEBACCESS_MSGS[8] body = "%s %s" % (CFG_WEBACCESS_MSGS[7], CFG_WEBACCESS_MSGS[2]) elif CFG_ACCESS_CONTROL_LEVEL_SITE == 2: title = CFG_WEBACCESS_MSGS[6] body = "%s %s" % (CFG_WEBACCESS_MSGS[4], CFG_WEBACCESS_MSGS[2]) return page(title=title, language=ln, uid=getUid(req), body=body, navtrail=navtrail, req=req, navmenuid=navmenuid) -def getApacheUser(req): - """Return the ApacheUser taking it from the cookie of the request.""" - session = get_session(req) - return session.get('apache_user') - def getUid(req): """Return user ID taking it from the cookie of the request. Includes control mechanism for the guest users, inserting in the database table when need be, raising the cookie back to the client. User ID is set to 0 when client refuses cookie or we are in the read-only site operation mode. User ID is set to -1 when we are in the permission denied site operation mode. getUid(req) -> userId """ if hasattr(req, '_user_info'): return req._user_info['uid'] if CFG_ACCESS_CONTROL_LEVEL_SITE == 1: return 0 if CFG_ACCESS_CONTROL_LEVEL_SITE == 2: return -1 guest = 0 session = get_session(req) uid = session.get('uid', -1) if uid == -1: # first time, so create a guest user if CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: uid = session['uid'] = createGuestUser() guest = 1 else: if CFG_ACCESS_CONTROL_LEVEL_GUESTS == 0: session['uid'] = 0 return 0 else: return -1 else: if not hasattr(req, '_user_info') and 'user_info' in session: req._user_info = session['user_info'] req._user_info = collect_user_info(req, refresh=True) if guest == 0: guest = isGuestUser(uid) if guest: if CFG_ACCESS_CONTROL_LEVEL_GUESTS == 0: return uid elif CFG_ACCESS_CONTROL_LEVEL_GUESTS >= 1: return -1 else: res = run_sql("SELECT note FROM user WHERE id=%s", (uid, )) if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS == 0: return uid elif CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 1 and res and res[0][0] in [1, "1"]: return uid else: return -1 -def setApacheUser(req, apache_user): - """It sets the apache_user into the session, and raise the cookie to the client. - """ - if hasattr(req, '_user_info'): - del req._user_info - session = get_session(req) - session['apache_user'] = apache_user - user_info = collect_user_info(req, login_time=True) - session['user_info'] = user_info - req._user_info = user_info - session.save() - return apache_user - def setUid(req, uid, remember_me=False): """It sets the userId into the session, and raise the cookie to the client. """ if hasattr(req, '_user_info'): del req._user_info session = get_session(req) session['uid'] = uid if remember_me: session.set_timeout(86400) session.set_remember_me() if uid > 0: user_info = collect_user_info(req, login_time=True) session['user_info'] = user_info req._user_info = user_info else: del session['user_info'] session.save() return uid def session_param_del(req, key): """ Remove a given key from the session. """ session = get_session(req) del session[key] session.save() def session_param_set(req, key, value): """ Associate a VALUE to the session param KEY for the current session. """ session = get_session(req) session[key] = value session.save() def session_param_get(req, key): """ Return session parameter value associated with session parameter KEY for the current session. If the key doesn't exists raise KeyError. """ session = get_session(req) return session[key] def session_param_list(req): """ List all available session parameters. """ session = get_session(req) return session.keys() def get_last_login(uid): """Return the last_login datetime for uid if any, otherwise return the Epoch.""" res = run_sql('SELECT last_login FROM user WHERE id=%s', (uid, ), 1) if res and res[0][0]: return res[0][0] else: return datetime.datetime(1970, 1, 1) def get_user_info(uid, ln=CFG_SITE_LANG): """Get infos for a given user. @param uid: user id (int) @return: tuple: (uid, nickname, display_name) """ _ = gettext_set_language(ln) query = """SELECT id, nickname FROM user WHERE id=%s""" res = run_sql(query, (uid, )) if res: if res[0]: user = list(res[0]) if user[1]: user.append(user[1]) else: user[1] = str(user[0]) user.append(_("user") + ' #' + str(user[0])) return tuple(user) return (uid, '', _("N/A")) def get_uid_from_email(email): """Return the uid corresponding to an email. Return -1 when the email does not exists.""" try: res = run_sql("SELECT id FROM user WHERE email=%s", (email, )) if res: return res[0][0] else: return -1 except OperationalError: register_exception() return -1 def isGuestUser(uid): """It Checks if the userId corresponds to a guestUser or not isGuestUser(uid) -> boolean """ out = 1 try: res = run_sql("SELECT email FROM user WHERE id=%s LIMIT 1", (uid,), 1) if res: if res[0][0]: out = 0 except OperationalError: register_exception() return out def isUserSubmitter(user_info): """Return True if the user is a submitter for something; False otherwise.""" u_email = get_email(user_info['uid']) res = run_sql("SELECT email FROM sbmSUBMISSIONS WHERE email=%s LIMIT 1", (u_email,), 1) return len(res) > 0 def isUserReferee(user_info): """Return True if the user is a referee for something; False otherwise.""" if CFG_CERN_SITE: return True else: for (role_id, role_name, role_description) in acc_get_action_roles(acc_get_action_id('referee')): if acc_is_user_in_role(user_info, role_id): return True return False def isUserAdmin(user_info): """Return True if the user has some admin rights; False otherwise.""" return acc_find_possible_activities(user_info) != {} def isUserSuperAdmin(user_info): """Return True if the user is superadmin; False otherwise.""" if run_sql("""SELECT r.id FROM accROLE r LEFT JOIN user_accROLE ur ON r.id = ur.id_accROLE WHERE r.name = %s AND ur.id_user = %s AND ur.expiration>=NOW() LIMIT 1""", (SUPERADMINROLE, user_info['uid']), 1): return True return acc_firerole_check_user(user_info, load_role_definition(acc_get_role_id(SUPERADMINROLE))) def nickname_valid_p(nickname): """Check whether wanted NICKNAME supplied by the user is valid. At the moment we just check whether it is not empty, does not contain blanks or @, is not equal to `guest', etc. This check relies on re_invalid_nickname regexp (see above) Return 1 if nickname is okay, return 0 if it is not. """ if nickname and \ not(nickname.startswith(' ') or nickname.endswith(' ')) and \ nickname.lower() != 'guest': if not re_invalid_nickname.match(nickname): return 1 return 0 def email_valid_p(email): """Check whether wanted EMAIL address supplied by the user is valid. At the moment we just check whether it contains '@' and whether it doesn't contain blanks. We also check the email domain if CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN is set. Return 1 if email is okay, return 0 if it is not. """ if (email.find("@") <= 0) or (email.find(" ") > 0): return 0 elif CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN: if not email.endswith(CFG_ACCESS_CONTROL_LIMIT_REGISTRATION_TO_DOMAIN): return 0 return 1 def confirm_email(email): """Confirm the email. It returns None when there are problems, otherwise it return the uid involved.""" if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS == 0: activated = 1 elif CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS == 1: activated = 0 elif CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 2: return -1 run_sql('UPDATE user SET note=%s where email=%s', (activated, email)) res = run_sql('SELECT id FROM user where email=%s', (email, )) if res: if CFG_ACCESS_CONTROL_NOTIFY_ADMIN_ABOUT_NEW_ACCOUNTS: send_new_admin_account_warning(email, CFG_SITE_ADMIN_EMAIL) return res[0][0] else: return None def registerUser(req, email, passw, nickname, register_without_nickname=False, login_method=None, ln=CFG_SITE_LANG): """Register user with the desired values of NICKNAME, EMAIL and PASSW. If REGISTER_WITHOUT_NICKNAME is set to True, then ignore desired NICKNAME and do not set any. This is suitable for external authentications so that people can login without having to register an internal account first. Return 0 if the registration is successful, 1 if email is not valid, 2 if nickname is not valid, 3 if email is already in the database, 4 if nickname is already in the database, 5 when users cannot register themselves because of the site policy, 6 when the site is having problem contacting the user. If login_method is None or is equal to the key corresponding to local authentication, then CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS is taken in account for deciding the behaviour about registering. """ # is email valid? email = email.lower() if not email_valid_p(email): return 1 _ = gettext_set_language(ln) # is email already taken? res = run_sql("SELECT email FROM user WHERE email=%s", (email,)) if len(res) > 0: return 3 if register_without_nickname: # ignore desired nick and use default empty string one: nickname = "" else: # is nickname valid? if not nickname_valid_p(nickname): return 2 # is nickname already taken? res = run_sql("SELECT nickname FROM user WHERE nickname=%s", (nickname,)) if len(res) > 0: return 4 activated = 1 # By default activated - if not login_method or not CFG_EXTERNAL_AUTHENTICATION[login_method][0]: # local login + if not login_method or not CFG_EXTERNAL_AUTHENTICATION[login_method]: # local login if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 2: return 5 elif CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT: activated = 2 # Email confirmation required elif CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 1: activated = 0 # Administrator confirmation required if CFG_ACCESS_CONTROL_NOTIFY_USER_ABOUT_NEW_ACCOUNT: address_activation_key = mail_cookie_create_mail_activation(email) ip_address = req.remote_host or req.remote_ip try: if not send_email(CFG_SITE_SUPPORT_EMAIL, email, _("Account registration at %s") % CFG_SITE_NAME_INTL.get(ln, CFG_SITE_NAME), tmpl.tmpl_account_address_activation_email_body(email, address_activation_key, ip_address, ln)): return 1 except (smtplib.SMTPException, socket.error): return 6 # okay, go on and register the user: user_preference = get_default_user_preferences() uid = run_sql("INSERT INTO user (nickname, email, password, note, settings, last_login) " "VALUES (%s,%s,AES_ENCRYPT(email,%s),%s,%s, NOW())", (nickname, email, passw, activated, serialize_via_marshal(user_preference))) if activated == 1: # Ok we consider the user as logged in :-) setUid(req, uid) return 0 def updateDataUser(uid, email, nickname): """ Update user data. Used when a user changed his email or password or nickname. """ email = email.lower() if email == 'guest': return 0 if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS < 2: run_sql("update user set email=%s where id=%s", (email, uid)) if nickname and nickname != '': run_sql("update user set nickname=%s where id=%s", (nickname, uid)) return 1 def updatePasswordUser(uid, password): """Update the password of a user.""" if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS < 3: run_sql("update user set password=AES_ENCRYPT(email,%s) where id=%s", (password, uid)) return 1 def loginUser(req, p_un, p_pw, login_method): """It is a first simple version for the authentication of user. It returns the id of the user, for checking afterwards if the login is correct """ # p_un passed may be an email or a nickname: p_email = get_email_from_username(p_un) # go on with the old stuff based on p_email: - if not CFG_EXTERNAL_AUTHENTICATION.has_key(login_method): + if not login_method in CFG_EXTERNAL_AUTHENTICATION: return ([], p_email, p_pw, 12) - if CFG_EXTERNAL_AUTHENTICATION[login_method][0]: # External Authenthication + if CFG_EXTERNAL_AUTHENTICATION[login_method]: # External Authenthication try: - p_email = CFG_EXTERNAL_AUTHENTICATION[login_method][0].auth_user(p_email, p_pw, req) or CFG_EXTERNAL_AUTHENTICATION[login_method][0].auth_user(p_un, p_pw, req) ## We try to login with either the email of the nickname + p_email = CFG_EXTERNAL_AUTHENTICATION[login_method].auth_user(p_email, p_pw, req) or CFG_EXTERNAL_AUTHENTICATION[login_method].auth_user(p_un, p_pw, req) ## We try to login with either the email of the nickname if p_email: p_email = p_email.lower() else: return([], p_email, p_pw, 15) except InvenioWebAccessExternalAuthError: - register_exception(alert_admin=True) + register_exception(req=req, alert_admin=True) raise if p_email: # Authenthicated externally query_result = run_sql("SELECT id from user where email=%s", (p_email,)) if not query_result: # First time user p_pw_local = int(random.random() * 1000000) p_nickname = '' - if CFG_EXTERNAL_AUTHENTICATION[login_method][0].enforce_external_nicknames: + if CFG_EXTERNAL_AUTHENTICATION[login_method].enforce_external_nicknames: try: # Let's discover the external nickname! - p_nickname = CFG_EXTERNAL_AUTHENTICATION[login_method][0].fetch_user_nickname(p_email, p_pw, req) + p_nickname = CFG_EXTERNAL_AUTHENTICATION[login_method].fetch_user_nickname(p_email, p_pw, req) except (AttributeError, NotImplementedError): pass except: - register_exception(alert_admin=True) + register_exception(req=req, alert_admin=True) raise res = registerUser(req, p_email, p_pw_local, p_nickname, register_without_nickname=p_nickname == '', login_method=login_method) if res == 4 or res == 2: # The nickname was already taken res = registerUser(req, p_email, p_pw_local, '', register_without_nickname=True, login_method=login_method) + query_result = run_sql("SELECT id from user where email=%s", (p_email,)) elif res == 0: # Everything was ok, with or without nickname. query_result = run_sql("SELECT id from user where email=%s", (p_email,)) elif res == 6: # error in contacting the user via email return([], p_email, p_pw_local, 19) else: return([], p_email, p_pw_local, 13) try: - groups = CFG_EXTERNAL_AUTHENTICATION[login_method][0].fetch_user_groups_membership(p_email, p_pw, req) + groups = CFG_EXTERNAL_AUTHENTICATION[login_method].fetch_user_groups_membership(p_email, p_pw, req) # groups is a dictionary {group_name : group_description,} new_groups = {} for key, value in groups.items(): new_groups[key + " [" + str(login_method) + "]"] = value groups = new_groups except (AttributeError, NotImplementedError): pass except: - register_exception(alert_admin=True) + register_exception(req=req, alert_admin=True) return([], p_email, p_pw, 16) else: # Groups synchronization - if groups != 0: + if groups: userid = query_result[0][0] from invenio.webgroup import synchronize_external_groups synchronize_external_groups(userid, groups, login_method) user_prefs = get_user_preferences(query_result[0][0]) - if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 4: - # Let's prevent the user to switch login_method - if user_prefs.has_key("login_method") and \ - user_prefs["login_method"] != login_method: - return([], p_email, p_pw, 11) - user_prefs["login_method"] = login_method + if not CFG_EXTERNAL_AUTHENTICATION[login_method]: + ## I.e. if the login method is not of robot type: + if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS >= 4: + # Let's prevent the user to switch login_method + if user_prefs.has_key("login_method") and \ + user_prefs["login_method"] != login_method: + return([], p_email, p_pw, 11) + user_prefs["login_method"] = login_method # Cleaning external settings for key in user_prefs.keys(): if key.startswith('EXTERNAL_'): del user_prefs[key] try: # Importing external settings - new_prefs = CFG_EXTERNAL_AUTHENTICATION[login_method][0].fetch_user_preferences(p_email, p_pw, req) + new_prefs = CFG_EXTERNAL_AUTHENTICATION[login_method].fetch_user_preferences(p_email, p_pw, req) for key, value in new_prefs.items(): user_prefs['EXTERNAL_' + key] = value except (AttributeError, NotImplementedError): pass except InvenioWebAccessExternalAuthError: - register_exception(alert_admin=True) + register_exception(req=req, alert_admin=True) return([], p_email, p_pw, 16) # Storing settings set_user_preferences(query_result[0][0], user_prefs) else: return ([], p_un, p_pw, 10) else: # Internal Authenthication if not p_pw: p_pw = '' query_result = run_sql("SELECT id,email,note from user where email=%s and password=AES_ENCRYPT(email,%s)", (p_email, p_pw,)) if query_result: #FIXME drop external groups and settings note = query_result[0][2] if note == '1': # Good account preferred_login_method = get_user_preferences(query_result[0][0])['login_method'] p_email = query_result[0][1].lower() if login_method != preferred_login_method: - if CFG_EXTERNAL_AUTHENTICATION.has_key(preferred_login_method): + if preferred_login_method in CFG_EXTERNAL_AUTHENTICATION: return ([], p_email, p_pw, 11) elif note == '2': # Email address need to be confirmed by user return ([], p_email, p_pw, 17) elif note == '0': # Account need to be confirmed by administrator return ([], p_email, p_pw, 18) else: return ([], p_email, p_pw, 14) # Login successful! Updating the last access time run_sql("UPDATE user SET last_login=NOW() WHERE email=%s", (p_email, )) return (query_result, p_email, p_pw, 0) def drop_external_settings(userId): """Drop the external (EXTERNAL_) settings of userid.""" prefs = get_user_preferences(userId) for key in prefs.keys(): if key.startswith('EXTERNAL_'): del prefs[key] set_user_preferences(userId, prefs) def logoutUser(req): """It logout the user of the system, creating a guest user. """ session = get_session(req) if CFG_WEBSESSION_DIFFERENTIATE_BETWEEN_GUESTS: uid = createGuestUser() session['uid'] = uid session.save() else: uid = 0 session.invalidate() if hasattr(req, '_user_info'): delattr(req, '_user_info') return uid def username_exists_p(username): """Check if USERNAME exists in the system. Username may be either nickname or email. Return 1 if it does exist, 0 if it does not. """ if username == "": # return not exists if asked for guest users return 0 res = run_sql("SELECT email FROM user WHERE email=%s", (username,)) + \ run_sql("SELECT email FROM user WHERE nickname=%s", (username,)) if len(res) > 0: return 1 return 0 def emailUnique(p_email): """Check if the email address only exists once. If yes, return userid, if not, -1 """ query_result = run_sql("select id, email from user where email=%s", (p_email,)) if len(query_result) == 1: return query_result[0][0] elif len(query_result) == 0: return 0 return -1 def nicknameUnique(p_nickname): """Check if the nickname only exists once. If yes, return userid, if not, -1 """ query_result = run_sql("select id, nickname from user where nickname=%s", (p_nickname,)) if len(query_result) == 1: return query_result[0][0] elif len(query_result) == 0: return 0 return -1 def update_Uid(req, p_email, remember_me=False): """It updates the userId of the session. It is used when a guest user is logged in succesfully in the system with a given email and password. As a side effect it will discover all the restricted collection to which the user has right to """ query_ID = int(run_sql("select id from user where email=%s", (p_email,))[0][0]) setUid(req, query_ID, remember_me) return query_ID def send_new_admin_account_warning(new_account_email, send_to, ln=CFG_SITE_LANG): """Send an email to the address given by send_to about the new account new_account_email.""" _ = gettext_set_language(ln) sub = _("New account on") + " '%s'" % CFG_SITE_NAME if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS == 1: sub += " - " + _("PLEASE ACTIVATE") body = _("A new account has been created on") + " '%s'" % CFG_SITE_NAME if CFG_ACCESS_CONTROL_LEVEL_ACCOUNTS == 1: body += _(" and is awaiting activation") body += ":\n\n" body += _(" Username/Email") + ": %s\n\n" % new_account_email body += _("You can approve or reject this account request at") + ": %s/admin/webaccess/webaccessadmin.py/manageaccounts\n" % CFG_SITE_URL return send_email(CFG_SITE_SUPPORT_EMAIL, send_to, subject=sub, content=body) def get_email(uid): """Return email address of the user uid. Return string 'guest' in case the user is not found.""" out = "guest" res = run_sql("SELECT email FROM user WHERE id=%s", (uid,), 1) if res and res[0][0]: out = res[0][0].lower() return out def get_email_from_username(username): """Return email address of the user corresponding to USERNAME. The username may be either nickname or email. Return USERNAME untouched if not found in the database or if found several matching entries. """ if username == '': return '' out = username res = run_sql("SELECT email FROM user WHERE email=%s", (username,), 1) + \ run_sql("SELECT email FROM user WHERE nickname=%s", (username,), 1) if res and len(res) == 1: out = res[0][0].lower() return out #def get_password(uid): #"""Return password of the user uid. Return None in case #the user is not found.""" #out = None #res = run_sql("SELECT password FROM user WHERE id=%s", (uid,), 1) #if res and res[0][0] != None: #out = res[0][0] #return out def get_nickname(uid): """Return nickname of the user uid. Return None in case the user is not found.""" out = None res = run_sql("SELECT nickname FROM user WHERE id=%s", (uid,), 1) if res and res[0][0]: out = res[0][0] return out def get_nickname_or_email(uid): """Return nickname (preferred) or the email address of the user uid. Return string 'guest' in case the user is not found.""" out = "guest" res = run_sql("SELECT nickname, email FROM user WHERE id=%s", (uid,), 1) if res and res[0]: if res[0][0]: out = res[0][0] elif res[0][1]: out = res[0][1].lower() return out def create_userinfobox_body(req, uid, language="en"): """Create user info box body for user UID in language LANGUAGE.""" if req: if req.subprocess_env.has_key('HTTPS') \ and req.subprocess_env['HTTPS'] == 'on': url_referer = CFG_SITE_SECURE_URL + req.unparsed_uri else: url_referer = CFG_SITE_URL + req.unparsed_uri if '/youraccount/logout' in url_referer: url_referer = '' else: url_referer = CFG_SITE_URL user_info = collect_user_info(req) try: return tmpl.tmpl_create_userinfobox(ln=language, url_referer=url_referer, guest = isGuestUser(uid), username = get_nickname_or_email(uid), submitter = user_info['precached_viewsubmissions'], referee = user_info['precached_useapprove'], admin = user_info['precached_useadmin'], usebaskets = user_info['precached_usebaskets'], usemessages = user_info['precached_usemessages'], usealerts = user_info['precached_usealerts'], usegroups = user_info['precached_usegroups'], useloans = user_info['precached_useloans'], usestats = user_info['precached_usestats'] ) except OperationalError: return "" def create_useractivities_menu(req, uid, navmenuid, ln="en"): """Create user activities menu. @param req: request object @param uid: user id @type uid: int @param navmenuid: the section of the website this page belongs (search, submit, baskets, etc.) @type navmenuid: string @param ln: language @type ln: string @return: HTML menu of the user activities @rtype: string """ if req: if req.subprocess_env.has_key('HTTPS') \ and req.subprocess_env['HTTPS'] == 'on': url_referer = CFG_SITE_SECURE_URL + req.unparsed_uri else: url_referer = CFG_SITE_URL + req.unparsed_uri if '/youraccount/logout' in url_referer: url_referer = '' else: url_referer = CFG_SITE_URL user_info = collect_user_info(req) is_user_menu_selected = False if navmenuid == 'personalize' or \ navmenuid.startswith('your') and \ navmenuid != 'youraccount': is_user_menu_selected = True try: return tmpl.tmpl_create_useractivities_menu( ln=ln, selected=is_user_menu_selected, url_referer=url_referer, guest = isGuestUser(uid), username = get_nickname_or_email(uid), submitter = user_info['precached_viewsubmissions'], referee = user_info['precached_useapprove'], admin = user_info['precached_useadmin'], usebaskets = user_info['precached_usebaskets'], usemessages = user_info['precached_usemessages'], usealerts = user_info['precached_usealerts'], usegroups = user_info['precached_usegroups'], useloans = user_info['precached_useloans'], usestats = user_info['precached_usestats'] ) except OperationalError: return "" def create_adminactivities_menu(req, uid, navmenuid, ln="en"): """Create admin activities menu. @param req: request object @param uid: user id @type uid: int @param navmenuid: the section of the website this page belongs (search, submit, baskets, etc.) @type navmenuid: string @param ln: language @type ln: string @return: HTML menu of the user activities @rtype: string """ _ = gettext_set_language(ln) if req: if req.subprocess_env.has_key('HTTPS') \ and req.subprocess_env['HTTPS'] == 'on': url_referer = CFG_SITE_SECURE_URL + req.unparsed_uri else: url_referer = CFG_SITE_URL + req.unparsed_uri if '/youraccount/logout' in url_referer: url_referer = '' else: url_referer = CFG_SITE_URL user_info = collect_user_info(req) activities = acc_find_possible_activities(user_info, ln) # For BibEdit and BibDocFile menu items, take into consideration # current record whenever possible if activities.has_key(_("Run Record Editor")) or \ activities.has_key(_("Run Document File Manager")) and \ user_info['uri'].startswith('/record/'): try: # Get record ID and try to cast it to an int current_record_id = int(urlparse.urlparse(user_info['uri'])[2].split('/')[2]) except: pass else: if activities.has_key(_("Run Record Editor")): activities[_("Run Record Editor")] = activities[_("Run Record Editor")] + '&#state=edit&recid=' + str(current_record_id) if activities.has_key(_("Run File Manager")): activities[_("Run File Manager")] = activities[_("Run File Manager")] + '&recid=' + str(current_record_id) try: return tmpl.tmpl_create_adminactivities_menu( ln=ln, selected=navmenuid == 'admin', url_referer=url_referer, guest = isGuestUser(uid), username = get_nickname_or_email(uid), submitter = user_info['precached_viewsubmissions'], referee = user_info['precached_useapprove'], admin = user_info['precached_useadmin'], usebaskets = user_info['precached_usebaskets'], usemessages = user_info['precached_usemessages'], usealerts = user_info['precached_usealerts'], usegroups = user_info['precached_usegroups'], useloans = user_info['precached_useloans'], usestats = user_info['precached_usestats'], activities = activities ) except OperationalError: return "" def list_registered_users(): """List all registered users.""" return run_sql("SELECT id,email FROM user where email!=''") def list_users_in_role(role): """List all users of a given role (see table accROLE) @param role: role of user (string) @return: list of uids """ res = run_sql("""SELECT uacc.id_user FROM user_accROLE uacc JOIN accROLE acc ON uacc.id_accROLE=acc.id WHERE acc.name=%s""", (role,)) if res: return map(lambda x: int(x[0]), res) return [] def list_users_in_roles(role_list): """List all users of given roles (see table accROLE) @param role_list: list of roles [string] @return: list of uids """ if not(type(role_list) is list or type(role_list) is tuple): role_list = [role_list] query = """SELECT DISTINCT(uacc.id_user) FROM user_accROLE uacc JOIN accROLE acc ON uacc.id_accROLE=acc.id """ query_addons = "" query_params = () if len(role_list) > 0: query_params = role_list query_addons = " WHERE " for role in role_list[:-1]: query_addons += "acc.name=%s OR " query_addons += "acc.name=%s" res = run_sql(query + query_addons, query_params) if res: return map(lambda x: int(x[0]), res) return [] def get_uid_based_on_pref(prefname, prefvalue): """get the user's UID based where his/her preference prefname has value prefvalue in preferences""" prefs = run_sql("SELECT id, settings FROM user WHERE settings is not NULL") the_uid = None for pref in prefs: try: settings = deserialize_via_marshal(pref[1]) if (settings.has_key(prefname)) and (settings[prefname] == prefvalue): the_uid = pref[0] except: pass return the_uid def get_user_preferences(uid): pref = run_sql("SELECT id, settings FROM user WHERE id=%s", (uid,)) if pref: try: return deserialize_via_marshal(pref[0][1]) except: pass return get_default_user_preferences() # empty dict mean no preferences def set_user_preferences(uid, pref): assert(type(pref) == type({})) run_sql("UPDATE user SET settings=%s WHERE id=%s", (serialize_via_marshal(pref), uid)) def get_default_user_preferences(): user_preference = { 'login_method': ''} - for system in CFG_EXTERNAL_AUTHENTICATION.keys(): - if CFG_EXTERNAL_AUTHENTICATION[system][1]: - user_preference['login_method'] = system - break + if CFG_EXTERNAL_AUTH_DEFAULT in CFG_EXTERNAL_AUTHENTICATION: + user_preference['login_method'] = CFG_EXTERNAL_AUTH_DEFAULT return user_preference def get_preferred_user_language(req): def _get_language_from_req_header(accept_language_header): """Extract langs info from req.headers_in['Accept-Language'] which should be set to something similar to: 'fr,en-us;q=0.7,en;q=0.3' """ tmp_langs = {} for lang in accept_language_header.split(','): lang = lang.split(';q=') if len(lang) == 2: lang[1] = lang[1].replace('"', '') # Hack for Yeti robot try: tmp_langs[float(lang[1])] = lang[0] except ValueError: pass else: tmp_langs[1.0] = lang[0] ret = [] priorities = tmp_langs.keys() priorities.sort() priorities.reverse() for priority in priorities: ret.append(tmp_langs[priority]) return ret uid = getUid(req) guest = isGuestUser(uid) new_lang = None preferred_lang = None if not guest: user_preferences = get_user_preferences(uid) preferred_lang = new_lang = user_preferences.get('language', None) if not new_lang: try: new_lang = wash_languages(cgi.parse_qs(req.args)['ln']) except (TypeError, AttributeError, KeyError): pass if not new_lang: try: new_lang = wash_languages(_get_language_from_req_header(req.headers_in['Accept-Language'])) except (TypeError, AttributeError, KeyError): pass new_lang = wash_language(new_lang) if new_lang != preferred_lang and not guest: user_preferences['language'] = new_lang set_user_preferences(uid, user_preferences) return new_lang def collect_user_info(req, login_time=False, refresh=False): """Given the mod_python request object rec or a uid it returns a dictionary containing at least the keys uid, nickname, email, groups, plus any external keys in the user preferences (collected at login time and built by the different external authentication plugins) and if the mod_python request object is provided, also the remote_ip, remote_host, referer, agent fields. - If the user is authenticated with Apache should provide also - apache_user and apache_group. NOTE: if req is a mod_python request object, the user_info dictionary is saved into req._user_info (for caching purpouses) setApacheUser & setUid will properly reset it. """ from invenio.search_engine import get_permitted_restricted_collections user_info = { 'remote_ip' : '', 'remote_host' : '', 'referer' : '', 'uri' : '', 'agent' : '', - 'apache_user' : '', - 'apache_group' : [], 'uid' : -1, 'nickname' : '', 'email' : '', 'group' : [], 'guest' : '1', 'session' : None, 'precached_permitted_restricted_collections' : [], 'precached_usebaskets' : False, 'precached_useloans' : False, 'precached_usegroups' : False, 'precached_usealerts' : False, 'precached_usemessages' : False, 'precached_viewsubmissions' : False, 'precached_useapprove' : False, 'precached_useadmin' : False, 'precached_usestats' : False, } try: is_req = False - if req is None: + if not req: uid = -1 elif type(req) in (type(1), type(1L)): ## req is infact a user identification uid = req elif type(req) is dict: ## req is by mistake already a user_info try: assert(req.has_key('uid')) assert(req.has_key('email')) assert(req.has_key('nickname')) except AssertionError: ## mmh... misuse of collect_user_info. Better warn the admin! register_exception(alert_admin=True) user_info.update(req) return user_info else: is_req = True uid = getUid(req) if hasattr(req, '_user_info') and not login_time: user_info = req._user_info if not refresh: return req._user_info req._user_info = user_info try: user_info['remote_ip'] = req.remote_ip except gaierror: #FIXME: we should support IPV6 too. (hint for FireRole) pass user_info['session'] = get_session(req).sid() user_info['remote_host'] = req.remote_host or '' user_info['referer'] = req.headers_in.get('Referer', '') user_info['uri'] = req.unparsed_uri or () user_info['agent'] = req.headers_in.get('User-Agent', 'N/A') - try: - user_info['apache_user'] = getApacheUser(req) - if user_info['apache_user']: - user_info['apache_group'] = auth_apache_user_in_groups(user_info['apache_user']) - except AttributeError: - pass user_info['uid'] = uid user_info['nickname'] = get_nickname(uid) or '' user_info['email'] = get_email(uid) or '' user_info['group'] = [] user_info['guest'] = str(isGuestUser(uid)) if user_info['guest'] == '0': user_info['group'] = [group[1] for group in get_groups(uid)] prefs = get_user_preferences(uid) login_method = prefs['login_method'] - login_object = CFG_EXTERNAL_AUTHENTICATION[login_method][0] + login_object = CFG_EXTERNAL_AUTHENTICATION[login_method] if login_object and ((datetime.datetime.now() - get_last_login(uid)).seconds > 3600): ## The user uses an external authentication method and it's a bit since ## she has not performed a login if not CFG_EXTERNAL_AUTH_USING_SSO or ( is_req and req.is_https()): ## If we're using SSO we must be sure to be in HTTPS ## otherwise we can't really read anything, hence ## it's better skeep the synchronization try: groups = login_object.fetch_user_groups_membership(user_info['email'], req=req) # groups is a dictionary {group_name : group_description,} new_groups = {} for key, value in groups.items(): new_groups[key + " [" + str(login_method) + "]"] = value groups = new_groups except (AttributeError, NotImplementedError, TypeError, InvenioWebAccessExternalAuthError): pass else: # Groups synchronization from invenio.webgroup import synchronize_external_groups synchronize_external_groups(uid, groups, login_method) user_info['group'] = [group[1] for group in get_groups(uid)] try: # Importing external settings new_prefs = login_object.fetch_user_preferences(user_info['email'], req=req) for key, value in new_prefs.items(): prefs['EXTERNAL_' + key] = value except (AttributeError, NotImplementedError, TypeError, InvenioWebAccessExternalAuthError): pass else: set_user_preferences(uid, prefs) prefs = get_user_preferences(uid) run_sql('UPDATE user SET last_login=NOW() WHERE id=%s', (uid, )) if prefs: for key, value in prefs.iteritems(): user_info[key.lower()] = value if login_time: ## Heavy computational information from invenio.access_control_engine import acc_authorize_action if CFG_WEBSEARCH_PERMITTED_RESTRICTED_COLLECTIONS_LEVEL > 0: user_info['precached_permitted_restricted_collections'] = get_permitted_restricted_collections(user_info) user_info['precached_usebaskets'] = acc_authorize_action(user_info, 'usebaskets')[0] == 0 user_info['precached_useloans'] = acc_authorize_action(user_info, 'useloans')[0] == 0 user_info['precached_usegroups'] = acc_authorize_action(user_info, 'usegroups')[0] == 0 user_info['precached_usealerts'] = acc_authorize_action(user_info, 'usealerts')[0] == 0 user_info['precached_usemessages'] = acc_authorize_action(user_info, 'usemessages')[0] == 0 user_info['precached_usestats'] = acc_authorize_action(user_info, 'runwebstatadmin')[0] == 0 user_info['precached_viewsubmissions'] = isUserSubmitter(user_info) user_info['precached_useapprove'] = isUserReferee(user_info) user_info['precached_useadmin'] = isUserAdmin(user_info) except Exception, e: register_exception() return user_info - -## --- follow some functions for Apache user/group authentication - -def _load_apache_password_file(apache_password_file=CFG_APACHE_PASSWORD_FILE): - ret = {} - for row in open(os.path.join(CFG_TMPDIR, apache_password_file)): - row = row.split(':') - if len(row) == 2: - ret[row[0].strip()] = row[1].strip() - return ret -_apache_passwords = _load_apache_password_file() - -def auth_apache_user_p(user, password): - """Check whether user-supplied credentials correspond to valid - Apache password data file.""" - if user in _apache_passwords: - password_apache = _apache_passwords[user] - salt = password_apache[:2] - return crypt.crypt(password, salt) == password_apache - else: - return False - -def _load_apache_group_file(apache_group_file=CFG_APACHE_GROUP_FILE): - ret = {} - for row in open(os.path.join(CFG_TMPDIR, apache_group_file)): - row = row.split(':') - if len(row) == 2: - group = row[0].strip() - users = row[1].strip().split(' ') - for user in users: - user = user.strip() - if user not in ret: - ret[user] = [] - ret[user].append(group) - return ret -_apache_groups = _load_apache_group_file() - -def auth_apache_user_in_groups(user): - """Return list of Apache groups to which Apache user belong.""" - return _apache_groups.get(user, []) - -def http_get_credentials(req): - if req.headers_in.has_key("Authorization"): - try: - s = req.headers_in["Authorization"][6:] - s = base64.decodestring(s) - user, passwd = s.split(":", 1) - except (ValueError, base64.binascii.Error, base64.binascii.Incomplete): - raise apache.SERVER_RETURN, apache.HTTP_BAD_REQUEST - return (user, passwd) - return (None, None) - -def http_check_credentials(req, role): - """Retrieve Apache password and check user credential with the - check_auth function. If this function returns True check if the user - is enabled to the given role. If this is True, return, otherwise - popup a new apache login box. - """ - - authorized = False - while True: - if req.headers_in.has_key("Authorization"): - try: - s = req.headers_in["Authorization"][6:] - s = base64.decodestring(s) - user, passwd = s.split(":", 1) - except (ValueError, base64.binascii.Error, base64.binascii.Incomplete): - raise apache.SERVER_RETURN, apache.HTTP_BAD_REQUEST - - authorized = auth_apache_user_p(user, passwd) - - if authorized: - setApacheUser(req, user) - authorized = acc_firerole_check_user(collect_user_info(req), load_role_definition(acc_get_role_id(role))) - setApacheUser(req, '') - - if not authorized: - # note that Opera supposedly doesn't like spaces around "=" below - s = 'Basic realm="%s"' % role - req.headers_out["WWW-Authenticate"] = s - raise apache.SERVER_RETURN, apache.HTTP_UNAUTHORIZED - else: - setApacheUser(req, user) - return diff --git a/modules/websession/lib/webuser_tests.py b/modules/websession/lib/webuser_tests.py index 939658dad..24525b0ce 100644 --- a/modules/websession/lib/webuser_tests.py +++ b/modules/websession/lib/webuser_tests.py @@ -1,87 +1,49 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Unit tests for the user handling library.""" __revision__ = "$Id$" import unittest from invenio import webuser from invenio.testutils import make_test_suite, run_test_suite from invenio.dbquery import run_sql -class ApacheAuthenticationTests(unittest.TestCase): - """Test functions related to the Apache authentication.""" - - def test_auth_apache_user_p(self): - """webuser - apache user password checking""" - # These should succeed: - self.assertEqual(True, - webuser.auth_apache_user_p('jekyll', 'j123ekyll')) - self.assertEqual(True, - webuser.auth_apache_user_p('hyde', 'h123yde')) - # Note: the following one should succeed even though the real - # password is different, because crypt() looks at first 8 - # chars only: - self.assertEqual(True, - webuser.auth_apache_user_p('jekyll', 'j123ekylx')) - # Now some attempts that should fail: - self.assertEqual(False, - webuser.auth_apache_user_p('jekyll', '')) - self.assertEqual(False, - webuser.auth_apache_user_p('jekyll', 'h123yde')) - self.assertEqual(False, - webuser.auth_apache_user_p('jekyll', 'aoeuidhtns')) - self.assertEqual(False, - webuser.auth_apache_user_p('aoeui', '')) - self.assertEqual(False, - webuser.auth_apache_user_p('aoeui', 'h123yde')) - self.assertEqual(False, - webuser.auth_apache_user_p('aoeui', 'dhtns')) - - def test_auth_apache_user_in_groups(self): - """webuser - apache user group membership checking""" - self.assertEqual(['theses'], - webuser.auth_apache_user_in_groups('jekyll')) - self.assertEqual([], - webuser.auth_apache_user_in_groups('hyde')) - self.assertEqual([], - webuser.auth_apache_user_in_groups('aoeui')) - class IsUserSuperAdminTests(unittest.TestCase): """Test functions related to the isUserSuperAdmin function.""" def setUp(self): self.id_admin = run_sql('SELECT id FROM user WHERE nickname="admin"')[0][0] self.id_hyde = run_sql('SELECT id FROM user WHERE nickname="hyde"')[0][0] def test_isUserSuperAdmin_admin(self): """webuser - isUserSuperAdmin with admin""" self.failUnless(webuser.isUserSuperAdmin(webuser.collect_user_info(self.id_admin))) def test_isUserSuperAdmin_hyde(self): """webuser - isUserSuperAdmin with hyde""" self.failIf(webuser.isUserSuperAdmin(webuser.collect_user_info(self.id_hyde))) -TEST_SUITE = make_test_suite(ApacheAuthenticationTests, IsUserSuperAdminTests) +TEST_SUITE = make_test_suite(IsUserSuperAdminTests) if __name__ == "__main__": run_test_suite(TEST_SUITE) diff --git a/modules/webstyle/img/Makefile.am b/modules/webstyle/img/Makefile.am index 1c9436f79..338b6a852 100644 --- a/modules/webstyle/img/Makefile.am +++ b/modules/webstyle/img/Makefile.am @@ -1,237 +1,238 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. imgdir=$(localstatedir)/www/img img_DATA = add-small.png \ add.png \ answer_bad.gif \ arrow_down.gif \ arrow_down2.png \ arrow_link-icon-15x11-right.gif \ arrow_up.gif \ arrow_up2.png \ at.gif \ balloon_arrow_left_shadow.png \ balloon_bottom_left_shadow.png \ balloon_bottom_shadow.png \ balloon_right_shadow.png \ balloon_top_right_shadow.png \ balloon_arrow_shadow.png \ balloon_bottom_right_shadow.png \ balloon_left_shadow.png \ balloon_top_left_shadow.png \ balloon_top_shadow.png \ blankicon.gif \ blue_gradient.gif \ bullet_toggle_minus.png \ bullet_toggle_plus.png \ circle_green.png \ circle_red.png \ cross_red.gif \ delete-big.png \ delete-small.png \ delete.png \ description.gif \ diff.png \ dot.gif \ down-trans.gif \ down.gif \ drop_down_menu_arrow_down_g.gif \ drop_down_menu_arrow_down_b.gif \ drop_down_menu_arrow_down_lb.gif \ drop_down_menu_arrow_down_w.gif \ edit1.gif \ external-icon-light-8x8.gif \ feed-icon-12x12.gif \ file-icon-none-96x128.gif \ file-icon-text-12x16.gif \ file-icon-text-15x20.gif \ file-icon-text-34x48.gif \ file-icon-text-96x128.gif \ forbidden_left.gif \ forbidden_right.gif \ gradient-lightgray-1x100.gif \ gradient_tab-gray-1x23.gif \ gradient_tab_on-gray-1x23.gif \ group_admin.png \ head.gif \ header_background.gif \ help.png \ icon-journal_Athanasius_Kircher_Atlantis.gif \ icon-journal_hms_beagle.gif \ iconcross.gif \ iconcross2.gif \ + pix.png \ plus_orange.png \ iconeye.gif \ iconpen.gif \ indicator.gif \ journal-template1.gif \ journal-template2.gif \ journal-template3.gif \ journal-template4.gif \ journal_Athanasius_Kircher_Atlantis.gif \ journal_content.png \ journal_footer.png \ journal_footer2.png \ journal_header.png \ journal_hms_beagle.gif \ journal_new.png \ journal_virgin_forest.gif \ journal_water_dog.gif \ keep_sso_connection_alive.gif \ last-right-part-trans.gif \ left-part-topless-trans.gif \ left-part-trans.gif \ left-trans.gif \ left.gif \ line-up-trans.gif \ line.gif \ loading.gif \ mail-icon-12x8.gif \ mainmenu.gif \ merge-small.png \ merge.png \ mergeNC.png \ move.png \ move_from.gif \ move_to.gif \ noway.gif \ okay.gif \ out.gif \ paper-texture-128x128.gif \ paper_clip-72x72.gif \ r.gif \ rcorners-gray-1280x18.gif \ rcorners-gray-1280x60-folded.gif \ red_gradient.gif \ refresh.png \ replace.png \ restricted.gif \ right-part-topless-trans.gif \ right-part-trans.gif \ right-trans.gif \ right.gif \ rss.png \ sb.gif \ sbm_guide_accessnumber.png \ sbm_guide_approvals.png \ sbm_guide_approve_button.png \ sbm_guide_browse.png \ sbm_guide_description.png \ sbm_guide_login.png \ sbm_guide_logout.png \ sbm_guide_modify_button.png \ sbm_guide_revise_button.png \ sbm_guide_submissions.png \ sbm_guide_submit_button.png \ sbm_guide_subnumber.png \ sbm_guide_summary.png \ sclose.gif \ se.gif \ search.png \ site_logo.gif \ site_logo_rss.png \ site_logo_small.gif \ smallbin.gif \ smalldown.gif \ smallfiles.gif \ smallup.gif \ smchk_gr.gif \ smchk_rd.gif \ sn.gif \ sp.gif \ star-icon-30x30.gif \ star_dot-icon-30x30.gif \ star_empty-icon-30x30.gif \ star_half-icon-30x30.gif \ stars-0-0.png \ stars-0-5.png \ stars-1-0.png \ stars-1-5.png \ stars-2-0.png \ stars-2-5.png \ stars-3-0.png \ stars-3-5.png \ stars-4-0.png \ stars-4-5.png \ stars-5-0.png \ summary.gif \ table.png \ table_add.png \ table_delete.png \ table_multiple.png \ table_row_delete.png \ table_row_insert.png \ table_sort.png \ tick.gif \ tree_branch.gif \ up.gif \ user-icon-1-16x16.gif \ user-icon-1-20x20.gif \ user-icon-1-24x24.gif \ waiting_or.gif \ warning.png \ webbasket_create.png \ webbasket_create_small.png \ webbasket_delete.png \ webbasket_down.png \ webbasket_extern.png \ webbasket_intern.png \ webbasket_move.png \ webbasket_ugs.png \ webbasket_up.png \ webbasket_us.png \ webbasket_user.png \ webbasket_usergroup.png \ webbasket_usergroup_gray.png \ webbasket_world.png \ webbasket_ws.png \ wb-add-note.png \ wb-copy-item.png \ wb-create-basket.png \ wb-delete-basket.png \ wb-delete-item.png \ wb-edit-basket.png \ wb-edit-topic.png \ wb-external-item.png \ wb-go-back.png \ wb-move-item-down-disabled.png \ wb-move-item-down.png \ wb-move-item-up-disabled.png \ wb-move-item-up.png \ wb-next-item-disabled.png \ wb-next-item.png \ wb-notes.png \ wb-previous-item-disabled.png \ wb-previous-item.png \ wb-sort-asc.gif \ wb-sort-desc.gif \ wb-sort-none.gif \ wb-subscribe.png \ wb-unsubscribe.png \ white_field.gif \ wsignout.gif \ compare.png tmpdir=$(localstatedir)/tmp tmp_DATA = plotextractor_dummy.png EXTRA_DIST = $(img_DATA) $(tmp_DATA) CLEANFILES = *~ *.tmp diff --git a/modules/webstyle/img/pix.png b/modules/webstyle/img/pix.png new file mode 100644 index 000000000..0da93fd36 Binary files /dev/null and b/modules/webstyle/img/pix.png differ diff --git a/modules/webstyle/lib/httptest_webinterface.py b/modules/webstyle/lib/httptest_webinterface.py index 4943a6184..96b453ad3 100644 --- a/modules/webstyle/lib/httptest_webinterface.py +++ b/modules/webstyle/lib/httptest_webinterface.py @@ -1,91 +1,98 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ HTTP Test web interface. This is the place where to put helpers for regression tests related to HTTP (or WSGI or SSO). """ __revision__ = \ "$Id$" __lastupdated__ = """$Date$""" import cgi from invenio.config import CFG_SITE_URL from invenio.webpage import page from invenio.webinterface_handler import WebInterfaceDirectory from invenio.urlutils import redirect_to_url class WebInterfaceHTTPTestPages(WebInterfaceDirectory): - _exports = ["", "post1", "post2", "sso", "dumpreq"] + _exports = ["", "post1", "post2", "sso", "dumpreq", "whatismyip"] def __call__(self, req, form): redirect_to_url(req, CFG_SITE_URL + '/httptest/post1') index = __call__ def sso(self, req, form): """ For testing single sign-on """ req.add_common_vars() sso_env = {} for var, value in req.subprocess_env.iteritems(): if var.startswith('HTTP_ADFS_'): sso_env[var] = value out = "SSO test</title</head>" out += "<body><table>" for var, value in sso_env.iteritems(): out += "<tr><td><strong>%s</strong></td><td>%s</td></tr>" % (var, value) out += "</table></body></html>" return out def dumpreq(self, req, form): """ Dump a textual representation of the request object. """ return "<pre>%s</pre>" % cgi.escape(str(req)) def post1(self, req, form): """ This is used by WSGI regression test, to test if it's possible to upload a file and retrieve it correctly. """ if req.method == 'POST': if 'file' in form: for row in form['file'].file: req.write(row) return '' else: body = """ <form method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" /> </form>""" return page("test1", body=body, req=req) def post2(self, req, form): """ This is to test L{handle_file_post} function. """ from invenio.webinterface_handler_wsgi_utils import handle_file_post from invenio.bibdocfile import stream_file if req.method != 'POST': body = """<p>Please send a file via POST.</p>""" return page("test2", body=body, req=req) path, mimetype = handle_file_post(req) - return stream_file(req, path, mime=mimetype) + return stream_file(req, path, mime=mimetype) + + def whatismyip(self, req, form): + """ + Return the client IP as seen by the server (useful for testing e.g. Robot authentication) + """ + req.content_type = "text/plain" + return req.remote_ip diff --git a/modules/websubmit/lib/websubmit_webinterface.py b/modules/websubmit/lib/websubmit_webinterface.py index ede41ac88..dce9b379c 100644 --- a/modules/websubmit/lib/websubmit_webinterface.py +++ b/modules/websubmit/lib/websubmit_webinterface.py @@ -1,1176 +1,1175 @@ ## This file is part of Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. __lastupdated__ = """$Date$""" __revision__ = "$Id$" import os import time import cgi import sys import shutil from urllib import urlencode from invenio.config import \ CFG_ACCESS_CONTROL_LEVEL_SITE, \ CFG_SITE_LANG, \ CFG_SITE_NAME, \ CFG_TMPDIR, \ CFG_SITE_NAME_INTL, \ CFG_SITE_URL, \ CFG_SITE_SECURE_URL, \ CFG_WEBSUBMIT_STORAGEDIR, \ CFG_PREFIX from invenio import webinterface_handler_config as apache from invenio.dbquery import run_sql from invenio.access_control_config import VIEWRESTRCOLL from invenio.access_control_mailcookie import mail_cookie_create_authorize_action from invenio.access_control_engine import acc_authorize_action from invenio.access_control_admin import acc_is_role from invenio.webpage import page, create_error_box, pageheaderonly, \ pagefooteronly from invenio.webuser import getUid, page_not_authorized, collect_user_info, isGuestUser, isUserSuperAdmin from invenio.websubmit_config import * from invenio.webinterface_handler import wash_urlargd, WebInterfaceDirectory from invenio.urlutils import make_canonical_urlargd, redirect_to_url from invenio.messages import gettext_set_language from invenio.search_engine import \ guess_primary_collection_of_a_record, get_colID, record_exists, \ create_navtrail_links, check_user_can_view_record, record_empty from invenio.bibdocfile import BibRecDocs, normalize_format, file_strip_ext, \ stream_restricted_icon, BibDoc, InvenioWebSubmitFileError, stream_file, \ decompose_file, propose_next_docname, get_subformat_from_format from invenio.errorlib import register_exception from invenio.websubmit_icon_creator import create_icon, InvenioWebSubmitIconCreatorError import invenio.template websubmit_templates = invenio.template.load('websubmit') from invenio.websearchadminlib import get_detailed_page_tabs from invenio.session import get_session import invenio.template webstyle_templates = invenio.template.load('webstyle') websearch_templates = invenio.template.load('websearch') try: from invenio.fckeditor_invenio_connector import FCKeditorConnectorInvenio fckeditor_available = True except ImportError, e: fckeditor_available = False from invenio.websubmit_managedocfiles import \ create_file_upload_interface, \ get_upload_file_interface_javascript, \ get_upload_file_interface_css, \ move_uploaded_files_to_storage class WebInterfaceFilesPages(WebInterfaceDirectory): def __init__(self,recid): self.recid = recid def _lookup(self, component, path): # after /record/<recid>/files/ every part is used as the file # name filename = component def getfile(req, form): args = wash_urlargd(form, websubmit_templates.files_default_urlargd) ln = args['ln'] _ = gettext_set_language(ln) uid = getUid(req) user_info = collect_user_info(req) verbose = args['verbose'] if verbose >= 1 and not isUserSuperAdmin(user_info): # Only SuperUser can see all the details! verbose = 0 if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE > 1: return page_not_authorized(req, "/record/%s" % self.recid, navmenuid='submit') if record_exists(self.recid) < 1: msg = "<p>%s</p>" % _("Requested record does not seem to exist.") return warningMsg(msg, req, CFG_SITE_NAME, ln) if record_empty(self.recid): msg = "<p>%s</p>" % _("Requested record does not seem to have been integrated.") return warningMsg(msg, req, CFG_SITE_NAME, ln) (auth_code, auth_message) = check_user_can_view_record(user_info, self.recid) - if auth_code and user_info['email'] == 'guest' and not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, {'collection' : guess_primary_collection_of_a_record(self.recid)}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : ln, 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target, norobot=True) elif auth_code: return page_not_authorized(req, "../", \ text = auth_message) readonly = CFG_ACCESS_CONTROL_LEVEL_SITE == 1 # From now on: either the user provided a specific file # name (and a possible version), or we return a list of # all the available files. In no case are the docids # visible. try: bibarchive = BibRecDocs(self.recid) except InvenioWebSubmitFileError, e: register_exception(req=req, alert_admin=True) msg = "<p>%s</p><p>%s</p>" % ( _("The system has encountered an error in retrieving the list of files for this document."), _("The error has been logged and will be taken in consideration as soon as possible.")) return warningMsg(msg, req, CFG_SITE_NAME, ln) if bibarchive.deleted_p(): return print_warning(req, _("Requested record does not seem to exist.")) docname = '' format = '' version = '' warn = '' if filename: # We know the complete file name, guess which docid it # refers to ## TODO: Change the extension system according to ext.py from setlink ## and have a uniform extension mechanism... docname = file_strip_ext(filename) format = filename[len(docname):] if format and format[0] != '.': format = '.' + format if args['subformat']: format += ';%s' % args['subformat'] else: docname = args['docname'] if not format: format = args['format'] if args['subformat']: format += ';%s' % args['subformat'] if not version: version = args['version'] # version could be either empty, or all or an integer try: int(version) except ValueError: if version != 'all': version = '' display_hidden = isUserSuperAdmin(user_info) if version != 'all': # search this filename in the complete list of files for doc in bibarchive.list_bibdocs(): if docname == doc.get_docname(): try: docfile = doc.get_file(format, version) (auth_code, auth_message) = docfile.is_restricted(user_info) if auth_code != 0: if CFG_WEBSUBMIT_ICON_SUBFORMAT_RE.match(get_subformat_from_format(format)): return stream_restricted_icon(req) - if user_info['email'] == 'guest' and not user_info['apache_user']: + if user_info['email'] == 'guest': cookie = mail_cookie_create_authorize_action('viewrestrdoc', {'status' : docfile.get_status()}) target = '/youraccount/login' + \ make_canonical_urlargd({'action': cookie, 'ln' : ln, 'referer' : \ CFG_SITE_URL + user_info['uri']}, {}) redirect_to_url(req, target) else: req.status = apache.HTTP_UNAUTHORIZED warn += print_warning(_("This file is restricted: ") + auth_message) break if not docfile.hidden_p(): if not readonly: ip = str(req.remote_ip) res = doc.register_download(ip, version, format, uid) try: return docfile.stream(req) except InvenioWebSubmitFileError, msg: register_exception(req=req, alert_admin=True) req.status = apache.HTTP_INTERNAL_SERVER_ERROR return warningMsg(_("An error has happened in trying to stream the request file."), req, CFG_SITE_NAME, ln) else: req.status = apache.HTTP_UNAUTHORIZED warn = print_warning(_("The requested file is hidden and can not be accessed.")) except InvenioWebSubmitFileError, msg: register_exception(req=req, alert_admin=True) if docname and format and not warn: req.status = apache.HTTP_NOT_FOUND warn += print_warning(_("Requested file does not seem to exist.")) filelist = bibarchive.display("", version, ln=ln, verbose=verbose, display_hidden=display_hidden) t = warn + websubmit_templates.tmpl_filelist( ln=ln, recid=self.recid, docname=args['docname'], version=version, filelist=filelist) cc = guess_primary_collection_of_a_record(self.recid) unordered_tabs = get_detailed_page_tabs(get_colID(cc), self.recid, ln) ordered_tabs_id = [(tab_id, values['order']) for (tab_id, values) in unordered_tabs.iteritems()] ordered_tabs_id.sort(lambda x,y: cmp(x[1],y[1])) link_ln = '' if ln != CFG_SITE_LANG: link_ln = '?ln=%s' % ln tabs = [(unordered_tabs[tab_id]['label'], \ '%s/record/%s/%s%s' % (CFG_SITE_URL, self.recid, tab_id, link_ln), \ tab_id == 'files', unordered_tabs[tab_id]['enabled']) \ for (tab_id, order) in ordered_tabs_id if unordered_tabs[tab_id]['visible'] == True] top = webstyle_templates.detailed_record_container_top(self.recid, tabs, args['ln']) bottom = webstyle_templates.detailed_record_container_bottom(self.recid, tabs, args['ln']) title, description, keywords = websearch_templates.tmpl_record_page_header_content(req, self.recid, args['ln']) return pageheaderonly(title=title, navtrail=create_navtrail_links(cc=cc, aas=0, ln=ln) + \ ''' > <a class="navtrail" href="%s/record/%s">%s</a> > %s''' % \ (CFG_SITE_URL, self.recid, title, _("Access to Fulltext")), description="", keywords="keywords", uid=uid, language=ln, req=req, navmenuid='search', navtrail_append_title_p=0) + \ websearch_templates.tmpl_search_pagestart(ln) + \ top + t + bottom + \ websearch_templates.tmpl_search_pageend(ln) + \ pagefooteronly(lastupdated=__lastupdated__, language=ln, req=req) return getfile, [] def __call__(self, req, form): """Called in case of URLs like /record/123/files without trailing slash. """ args = wash_urlargd(form, websubmit_templates.files_default_urlargd) ln = args['ln'] link_ln = '' if ln != CFG_SITE_LANG: link_ln = '?ln=%s' % ln return redirect_to_url(req, '%s/record/%s/files/%s' % (CFG_SITE_URL, self.recid, link_ln)) def websubmit_legacy_getfile(req, form): """ Handle legacy /getfile.py URLs """ args = wash_urlargd(form, { 'recid': (int, 0), 'docid': (int, 0), 'version': (str, ''), 'name': (str, ''), 'format': (str, ''), 'ln' : (str, CFG_SITE_LANG) }) _ = gettext_set_language(args['ln']) def _getfile_py(req, recid=0, docid=0, version="", name="", format="", ln=CFG_SITE_LANG): if not recid: ## Let's obtain the recid from the docid if docid: try: bibdoc = BibDoc(docid=docid) recid = bibdoc.get_recid() except InvenioWebSubmitFileError, e: return warningMsg(_("An error has happened in trying to retrieve the requested file."), req, CFG_SITE_NAME, ln) else: return warningMsg(_('Not enough information to retrieve the document'), req, CFG_SITE_NAME, ln) else: if not name and docid: ## Let's obtain the name from the docid try: bibdoc = BibDoc(docid) name = bibdoc.get_docname() except InvenioWebSubmitFileError, e: return warningMsg(_("An error has happened in trying to retrieving the requested file."), req, CFG_SITE_NAME, ln) format = normalize_format(format) redirect_to_url(req, '%s/record/%s/files/%s%s?ln=%s%s' % (CFG_SITE_URL, recid, name, format, ln, version and 'version=%s' % version or ''), apache.HTTP_MOVED_PERMANENTLY) return _getfile_py(req, **args) # -------------------------------------------------- from invenio.websubmit_engine import home, action, interface, endaction class WebInterfaceSubmitPages(WebInterfaceDirectory): _exports = ['summary', 'sub', 'direct', '', 'attachfile', 'uploadfile', \ 'getuploadedfile', 'managedocfiles', 'managedocfilesasync'] def managedocfiles(self, req, form): """ Display admin interface to manage files of a record """ argd = wash_urlargd(form, { 'ln': (str, ''), 'access': (str, ''), 'recid': (int, None), 'do': (int, 0), 'cancel': (str, None), }) _ = gettext_set_language(argd['ln']) uid = getUid(req) user_info = collect_user_info(req) # Check authorization (auth_code, auth_msg) = acc_authorize_action(req, 'runbibdocfile') - if auth_code and user_info['email'] == 'guest' and \ - not user_info['apache_user']: + if auth_code and user_info['email'] == 'guest': # Ask to login target = '/youraccount/login' + \ make_canonical_urlargd({'ln' : argd['ln'], 'referer' : CFG_SITE_URL + user_info['uri']}, {}) return redirect_to_url(req, target) elif auth_code: return page_not_authorized(req, referer="/submit/managedocfiles", uid=uid, text=auth_msg, ln=argd['ln'], navmenuid="admin") # Prepare navtrail navtrail = '''<a class="navtrail" href="%(CFG_SITE_URL)s/help/admin">Admin Area</a> > %(manage_files)s''' \ % {'CFG_SITE_URL': CFG_SITE_URL, 'manage_files': _("Manage Document Files")} body = '' if argd['do'] != 0 and not argd['cancel']: # Apply modifications working_dir = os.path.join(CFG_TMPDIR, 'websubmit_upload_interface_config_' + str(uid), argd['access']) move_uploaded_files_to_storage(working_dir=working_dir, recid=argd['recid'], icon_sizes=['180>','700>'], create_icon_doctypes=['*'], force_file_revision=False) # Clean temporary directory shutil.rmtree(working_dir) # Confirm modifications body += '<p style="color:#0f0">%s</p>' % \ (_('Your modifications to record #%i have been submitted') % argd['recid']) elif argd['cancel']: # Clean temporary directory working_dir = os.path.join(CFG_TMPDIR, 'websubmit_upload_interface_config_' + str(uid), argd['access']) shutil.rmtree(working_dir) body += '<p style="color:#c00">%s</p>' % \ (_('Your modifications to record #%i have been cancelled') % argd['recid']) if not argd['recid'] or argd['do'] != 0: body += ''' <form method="post" action="%(CFG_SITE_URL)s/submit/managedocfiles"> <label for="recid">%(edit_record)s:</label> <input type="text" name="recid" id="recid" /> <input type="submit" value="%(edit)s" class="adminbutton" /> </form> ''' % {'edit': _('Edit'), 'edit_record': _('Edit record'), 'CFG_SITE_URL': CFG_SITE_URL} access = time.strftime('%Y%m%d_%H%M%S') if argd['recid'] and argd['do'] == 0: # Displaying interface to manage files # Prepare navtrail title, description, keywords = websearch_templates.tmpl_record_page_header_content(req, argd['recid'], argd['ln']) navtrail = '''<a class="navtrail" href="%(CFG_SITE_URL)s/help/admin">Admin Area</a> > <a class="navtrail" href="%(CFG_SITE_URL)s/submit/managedocfiles">%(manage_files)s</a> > %(record)s: %(title)s ''' \ % {'CFG_SITE_URL': CFG_SITE_URL, 'title': title, 'manage_files': _("Document File Manager"), 'record': _("Record #%i") % argd['recid']} # FIXME: add parameters to `runbibdocfile' in order to # configure the file editor based on role, or at least # move configuration below to some config file. body += create_file_upload_interface(\ recid=argd['recid'], ln=argd['ln'], doctypes_and_desc=[('main', 'Main document'), ('latex', 'LaTeX'), ('source', 'Source'), ('additional', 'Additional File'), ('audio', 'Audio file'), ('video', 'Video file'), ('script', 'Script'), ('data', 'Data'), ('figure', 'Figure'), ('schema', 'Schema'), ('graph', 'Graph'), ('image', 'Image'), ('drawing', 'Drawing'), ('slides', 'Slides')], can_revise_doctypes=['*'], can_comment_doctypes=['*'], can_describe_doctypes=['*'], can_delete_doctypes=['*'], can_keep_doctypes=['*'], can_rename_doctypes=['*'], can_add_format_to_doctypes=['*'], can_restrict_doctypes=['*'], restrictions_and_desc=[('', 'Public'), ('restricted', 'Restricted')], uid=uid, sbm_access=access)[1] body += '''<br /> <form method="post" action="%(CFG_SITE_URL)s/submit/managedocfiles"> <input type="hidden" name="recid" value="%(recid)s" /> <input type="hidden" name="do" value="1" /> <input type="hidden" name="access" value="%(access)s" /> <input type="hidden" name="ln" value="%(ln)s" /> <div style="font-size:small"> <input type="submit" name="cancel" value="%(cancel_changes)s" /> <input type="submit" onclick="user_must_confirm_before_leaving_page=false;return true;" class="adminbutton" name="submit" id="applyChanges" value="%(apply_changes)s" /> </div></form>''' % \ {'apply_changes': _("Apply changes"), 'cancel_changes': _("Cancel all changes"), 'recid': argd['recid'], 'access': access, 'ln': argd['ln'], 'CFG_SITE_URL': CFG_SITE_URL} body += websubmit_templates.tmpl_page_do_not_leave_submission_js(argd['ln'], enabled=True) return page(title = _("Document File Manager") + (argd['recid'] and (': ' + _("Record #%i") % argd['recid']) or ''), navtrail=navtrail, navtrail_append_title_p=0, metaheaderadd = get_upload_file_interface_javascript(form_url_params='?access='+access) + \ get_upload_file_interface_css(), body = body, uid = uid, language=argd['ln'], req=req, navmenuid='admin') def managedocfilesasync(self, req, form): "Upload file and returns upload interface" argd = wash_urlargd(form, { 'ln': (str, ''), 'recid': (int, 1), 'doctype': (str, ''), 'access': (str, ''), 'indir': (str, ''), }) user_info = collect_user_info(req) include_headers = False # User submitted either through WebSubmit, or admin interface. if form.has_key('doctype') and form.has_key('indir') \ and form.has_key('access'): # Submitted through WebSubmit. Check rights include_headers = True working_dir = os.path.join(CFG_WEBSUBMIT_STORAGEDIR, argd['indir'], argd['doctype'], argd['access']) try: assert(working_dir == os.path.abspath(working_dir)) except AssertionError: return apache.HTTP_UNAUTHORIZED try: # Retrieve recid from working_dir, safer. recid_fd = file(os.path.join(working_dir, 'SN')) recid = int(recid_fd.read()) recid_fd.close() except: recid = "" try: act_fd = file(os.path.join(working_dir, 'act')) action = act_fd.read() act_fd.close() except: action = "" # Is user authorized to perform this action? (auth_code, auth_msg) = acc_authorize_action(user_info, "submit", doctype=argd['doctype'], act=action) else: # User must be allowed to attach files (auth_code, auth_msg) = acc_authorize_action(user_info, 'runbibdocfile') recid = argd['recid'] if auth_code: return apache.HTTP_UNAUTHORIZED return create_file_upload_interface(recid=recid, ln=argd['ln'], print_outside_form_tag=False, print_envelope=False, form=form, include_headers=include_headers, sbm_indir=argd['indir'], sbm_access=argd['access'], sbm_doctype=argd['doctype'], uid=user_info['uid'])[1] def uploadfile(self, req, form): """ Similar to /submit, but only consider files. Nice for asynchronous Javascript uploads. Should be used to upload a single file. Also try to create an icon, and return URL to file(s) + icon(s) Authentication is performed based on session ID passed as parameter instead of cookie-based authentication, due to the use of this URL by the Flash plugin (to upload multiple files at once), which does not route cookies. FIXME: consider adding /deletefile and /modifyfile functions + parsing of additional parameters to rename files, add comments, restrictions, etc. """ if sys.hexversion < 0x2060000: try: import simplejson as json simplejson_available = True except ImportError: # Okay, no Ajax app will be possible, but continue anyway, # since this package is only recommended, not mandatory. simplejson_available = False else: import json simplejson_available = True argd = wash_urlargd(form, { 'doctype': (str, ''), 'access': (str, ''), 'indir': (str, ''), 'session_id': (str, ''), 'rename': (str, ''), }) curdir = None if not form.has_key("indir") or \ not form.has_key("doctype") or \ not form.has_key("access"): return apache.HTTP_BAD_REQUEST else: curdir = os.path.join(CFG_WEBSUBMIT_STORAGEDIR, argd['indir'], argd['doctype'], argd['access']) user_info = collect_user_info(req) if form.has_key("session_id"): # Are we uploading using Flash, which does not transmit # cookie? The expect to receive session_id as a form # parameter. First check that IP addresses do not # mismatch. A ValueError will be raises if there is # something wrong session = get_session(req=req, sid=argd['session_id']) try: session = get_session(req=req, sid=argd['session_id']) except ValueError, e: return apache.HTTP_BAD_REQUEST # Retrieve user information. We cannot rely on the session here. res = run_sql("SELECT uid FROM session WHERE session_key=%s", (argd['session_id'],)) if len(res): uid = res[0][0] user_info = collect_user_info(uid) try: act_fd = file(os.path.join(curdir, 'act')) action = act_fd.read() act_fd.close() except: act = "" # Is user authorized to perform this action? (auth_code, auth_message) = acc_authorize_action(uid, "submit", verbose=0, doctype=argd['doctype'], act=action) if acc_is_role("submit", doctype=argd['doctype'], act=action) and auth_code != 0: # User cannot submit return apache.HTTP_UNAUTHORIZED else: # Process the upload and get the response added_files = {} for key, formfields in form.items(): filename = key.replace("[]", "") file_to_open = os.path.join(curdir, filename) if hasattr(formfields, "filename") and formfields.filename: dir_to_open = os.path.abspath(os.path.join(curdir, 'files', str(user_info['uid']), key)) try: assert(dir_to_open.startswith(CFG_WEBSUBMIT_STORAGEDIR)) except AssertionError: register_exception(req=req, prefix='curdir="%s", key="%s"' % (curdir, key)) return apache.HTTP_FORBIDDEN if not os.path.exists(dir_to_open): try: os.makedirs(dir_to_open) except: register_exception(req=req, alert_admin=True) return apache.HTTP_FORBIDDEN filename = formfields.filename ## Before saving the file to disc, wash the filename (in particular ## washing away UNIX and Windows (e.g. DFS) paths): filename = os.path.basename(filename.split('\\')[-1]) filename = filename.strip() if filename != "": # Check that file does not already exist n = 1 while os.path.exists(os.path.join(dir_to_open, filename)): #dirname, basename, extension = decompose_file(new_destination_path) basedir, name, extension = decompose_file(filename) new_name = propose_next_docname(name) filename = new_name + extension # This may be dangerous if the file size is bigger than the available memory fp = open(os.path.join(dir_to_open, filename), "w") fp.write(formfields.file.read()) fp.close() fp = open(os.path.join(curdir, "lastuploadedfile"), "w") fp.write(filename) fp.close() fp = open(file_to_open, "w") fp.write(filename) fp.close() try: # Create icon (icon_path, icon_name) = create_icon( { 'input-file' : os.path.join(dir_to_open, filename), 'icon-name' : filename, # extension stripped automatically 'icon-file-format' : 'gif', 'multipage-icon' : False, 'multipage-icon-delay' : 100, 'icon-scale' : "300>", # Resize only if width > 300 'verbosity' : 0, }) icons_dir = os.path.join(os.path.join(curdir, 'icons', str(user_info['uid']), key)) if not os.path.exists(icons_dir): # Create uid/icons dir if needed os.makedirs(icons_dir) os.rename(os.path.join(icon_path, icon_name), os.path.join(icons_dir, icon_name)) added_files[key] = {'name': filename, 'iconName': icon_name} except InvenioWebSubmitIconCreatorError, e: # We could not create the icon added_files[key] = {'name': filename} continue else: return apache.HTTP_BAD_REQUEST # Send our response if simplejson_available: return json.dumps(added_files) def getuploadedfile(self, req, form): """ Stream uploaded files. For the moment, restrict to files in ./curdir/files/uid or ./curdir/icons/uid directory, so that we are sure we stream files only to the user who uploaded them. """ argd = wash_urlargd(form, {'indir': (str, None), 'doctype': (str, None), 'access': (str, None), 'icon': (int, 0), 'key': (str, None), 'filename': (str, None)}) if None in argd.values(): return apache.HTTP_BAD_REQUEST uid = getUid(req) if argd['icon']: file_path = os.path.join(CFG_WEBSUBMIT_STORAGEDIR, argd['indir'], argd['doctype'], argd['access'], 'icons', str(uid), argd['key'], argd['filename'] ) else: file_path = os.path.join(CFG_WEBSUBMIT_STORAGEDIR, argd['indir'], argd['doctype'], argd['access'], 'files', str(uid), argd['key'], argd['filename'] ) abs_file_path = os.path.abspath(file_path) if abs_file_path.startswith(CFG_WEBSUBMIT_STORAGEDIR): # Check if file exist. Note that icon might not yet have # been created. for i in range(5): if os.path.exists(abs_file_path): return stream_file(req, abs_file_path) time.sleep(1) # Send error 404 in all other cases return apache.HTTP_NOT_FOUND def attachfile(self, req, form): """ Process requests received from FCKeditor to upload files. If the uploaded file is an image, create an icon version """ if not fckeditor_available: return apache.HTTP_NOT_FOUND if not form.has_key('type'): form['type'] = 'File' if not form.has_key('NewFile') or \ not form['type'] in \ ['File', 'Image', 'Flash', 'Media']: return apache.HTTP_NOT_FOUND uid = getUid(req) # URL where the file can be fetched after upload user_files_path = '%(CFG_SITE_URL)s/submit/getattachedfile/%(uid)s' % \ {'uid': uid, 'CFG_SITE_URL': CFG_SITE_URL} # Path to directory where uploaded files are saved user_files_absolute_path = '%(CFG_PREFIX)s/var/tmp/attachfile/%(uid)s' % \ {'uid': uid, 'CFG_PREFIX': CFG_PREFIX} try: os.makedirs(user_files_absolute_path) except: pass # Create a Connector instance to handle the request conn = FCKeditorConnectorInvenio(form, recid=-1, uid=uid, allowed_commands=['QuickUpload'], allowed_types = ['File', 'Image', 'Flash', 'Media'], user_files_path = user_files_path, user_files_absolute_path = user_files_absolute_path) user_info = collect_user_info(req) (auth_code, auth_message) = acc_authorize_action(user_info, 'attachsubmissionfile') - if user_info['email'] == 'guest' and not user_info['apache_user']: + if user_info['email'] == 'guest': # User is guest: must login prior to upload data = conn.sendUploadResults(1, '', '', 'Please login before uploading file.') elif auth_code: # User cannot submit data = conn.sendUploadResults(1, '', '', 'Sorry, you are not allowed to submit files.') else: # Process the upload and get the response data = conn.doResponse() # At this point, the file has been uploaded. The FCKeditor # submit the image in form['NewFile']. However, the image # might have been renamed in between by the FCK connector on # the server side, by appending (%04d) at the end of the base # name. Retrieve that file uploaded_file_path = os.path.join(user_files_absolute_path, form['type'].lower(), form['NewFile'].filename) uploaded_file_path = retrieve_most_recent_attached_file(uploaded_file_path) uploaded_file_name = os.path.basename(uploaded_file_path) # Create an icon if form.get('type','') == 'Image': try: (icon_path, icon_name) = create_icon( { 'input-file' : uploaded_file_path, 'icon-name' : os.path.splitext(uploaded_file_name)[0], 'icon-file-format' : os.path.splitext(uploaded_file_name)[1][1:] or 'gif', 'multipage-icon' : False, 'multipage-icon-delay' : 100, 'icon-scale' : "300>", # Resize only if width > 300 'verbosity' : 0, }) # Move original file to /original dir, and replace it with icon file original_user_files_absolute_path = os.path.join(user_files_absolute_path, 'image', 'original') if not os.path.exists(original_user_files_absolute_path): # Create /original dir if needed os.mkdir(original_user_files_absolute_path) os.rename(uploaded_file_path, original_user_files_absolute_path + os.sep + uploaded_file_name) os.rename(icon_path + os.sep + icon_name, uploaded_file_path) except InvenioWebSubmitIconCreatorError, e: pass # Transform the headers into something ok for mod_python for header in conn.headers: if not header is None: if header[0] == 'Content-Type': req.content_type = header[1] else: req.headers_out[header[0]] = header[1] # Send our response req.send_http_header() req.write(data) def _lookup(self, component, path): """ This handler is invoked for the dynamic URLs (for getting and putting attachments) Eg: /submit/getattachedfile/41336978/image/myfigure.png /submit/attachfile/41336978/image/myfigure.png """ if component == 'getattachedfile' and len(path) > 2: uid = path[0] # uid of the submitter file_type = path[1] # file, image, flash or media (as # defined by FCKeditor) if file_type in ['file', 'image', 'flash', 'media']: file_name = '/'.join(path[2:]) # the filename def answer_get(req, form): """Accessing files attached to submission.""" form['file'] = file_name form['type'] = file_type form['uid'] = uid return self.getattachedfile(req, form) return answer_get, [] # All other cases: file not found return None, [] def getattachedfile(self, req, form): """ Returns a file uploaded to the submission 'drop box' by the FCKeditor. """ argd = wash_urlargd(form, {'file': (str, None), 'type': (str, None), 'uid': (int, 0)}) # Can user view this record, i.e. can user access its # attachments? uid = getUid(req) user_info = collect_user_info(req) if not argd['file'] is None: # Prepare path to file on disk. Normalize the path so that # ../ and other dangerous components are removed. path = os.path.abspath(CFG_PREFIX + '/var/tmp/attachfile/' + \ '/' + str(argd['uid']) + \ '/' + argd['type'] + '/' + argd['file']) # Check that we are really accessing attachements # directory, for the declared record. if path.startswith(CFG_PREFIX + '/var/tmp/attachfile/') and os.path.exists(path): return stream_file(req, path) # Send error 404 in all other cases return(apache.HTTP_NOT_FOUND) def direct(self, req, form): """Directly redirected to an initialized submission.""" args = wash_urlargd(form, {'sub': (str, ''), 'access' : (str, '')}) sub = args['sub'] access = args['access'] ln = args['ln'] _ = gettext_set_language(ln) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "direct", navmenuid='submit') myQuery = req.args if not sub: return warningMsg(_("Sorry, 'sub' parameter missing..."), req, ln=ln) res = run_sql("SELECT docname,actname FROM sbmIMPLEMENT WHERE subname=%s", (sub,)) if not res: return warningMsg(_("Sorry. Cannot analyse parameter"), req, ln=ln) else: # get document type doctype = res[0][0] # get action name action = res[0][1] # retrieve other parameter values params = dict(form) # find existing access number if not access: # create 'unique' access number pid = os.getpid() now = time.time() access = "%i_%s" % (now,pid) # retrieve 'dir' value res = run_sql ("SELECT dir FROM sbmACTION WHERE sactname=%s", (action,)) dir = res[0][0] mainmenu = req.headers_in.get('referer') params['access'] = access params['act'] = action params['doctype'] = doctype params['startPg'] = '1' params['mainmenu'] = mainmenu params['ln'] = ln params['indir'] = dir url = "%s/submit?%s" % (CFG_SITE_URL, urlencode(params)) redirect_to_url(req, url) def sub(self, req, form): """DEPRECATED: /submit/sub is deprecated now, so raise email to the admin (but allow submission to continue anyway)""" args = wash_urlargd(form, {'password': (str, '')}) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../sub/", navmenuid='submit') try: raise DeprecationWarning, 'submit/sub handler has been used. Please use submit/direct. e.g. "submit/sub?RN=123@SBIFOO" -> "submit/direct?RN=123&sub=SBIFOO"' except DeprecationWarning: register_exception(req=req, alert_admin=True) ln = args['ln'] _ = gettext_set_language(ln) #DEMOBOO_RN=DEMO-BOOK-2008-001&ln=en&password=1223993532.26572%40APPDEMOBOO params = dict(form) password = args['password'] if password: del params['password'] if "@" in password: params['access'], params['sub'] = password.split('@', 1) else: params['sub'] = password else: args = str(req.args).split('@') if len(args) > 1: params = {'sub' : args[-1]} args = '@'.join(args[:-1]) params.update(cgi.parse_qs(args)) else: return warningMsg(_("Sorry, invalid URL..."), req, ln=ln) url = "%s/submit/direct?%s" % (CFG_SITE_URL, urlencode(params, doseq=True)) redirect_to_url(req, url) def summary(self, req, form): args = wash_urlargd(form, { 'doctype': (str, ''), 'act': (str, ''), 'access': (str, ''), 'indir': (str, '')}) uid = getUid(req) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../summary", navmenuid='submit') t="" curdir = os.path.join(CFG_WEBSUBMIT_STORAGEDIR, args['indir'], args['doctype'], args['access']) try: assert(curdir == os.path.abspath(curdir)) except AssertionError: register_exception(req=req, alert_admin=True, prefix='Possible cracking tentative: indir="%s", doctype="%s", access="%s"' % (args['indir'], args['doctype'], args['access'])) return warningMsg("Invalid parameters", req) subname = "%s%s" % (args['act'], args['doctype']) res = run_sql("select sdesc,fidesc,pagenb,level from sbmFIELD where subname=%s " "order by pagenb,fieldnb", (subname,)) nbFields = 0 values = [] for arr in res: if arr[0] != "": val = { 'mandatory' : (arr[3] == 'M'), 'value' : '', 'page' : arr[2], 'name' : arr[0], } if os.path.exists(os.path.join(curdir, curdir,arr[1])): fd = open(os.path.join(curdir, arr[1]),"r") value = fd.read() fd.close() value = value.replace("\n"," ") value = value.replace("Select:","") else: value = "" val['value'] = value values.append(val) return websubmit_templates.tmpl_submit_summary( ln = args['ln'], values = values, ) def index(self, req, form): args = wash_urlargd(form, { 'c': (str, CFG_SITE_NAME), 'doctype': (str, ''), 'act': (str, ''), 'startPg': (str, "1"), 'access': (str, ''), 'mainmenu': (str, ''), 'fromdir': (str, ''), 'nextPg': (str, ''), 'nbPg': (str, ''), 'curpage': (str, '1'), 'step': (str, '0'), 'mode': (str, 'U'), }) ## Strip whitespace from beginning and end of doctype and action: args["doctype"] = args["doctype"].strip() args["act"] = args["act"].strip() def _index(req, c, ln, doctype, act, startPg, access, mainmenu, fromdir, nextPg, nbPg, curpage, step, mode): uid = getUid(req) if isGuestUser(uid): return redirect_to_url(req, "%s/youraccount/login%s" % ( CFG_SITE_SECURE_URL, make_canonical_urlargd({ 'referer' : CFG_SITE_URL + req.unparsed_uri, 'ln' : args['ln']}, {})), norobot=True) if uid == -1 or CFG_ACCESS_CONTROL_LEVEL_SITE >= 1: return page_not_authorized(req, "../submit", navmenuid='submit') if doctype=="": return home(req,c,ln) elif act=="": return action(req,c,ln,doctype) elif int(step)==0: return interface(req, c, ln, doctype, act, startPg, access, mainmenu, fromdir, nextPg, nbPg, curpage) else: return endaction(req, c, ln, doctype, act, startPg, access,mainmenu, fromdir, nextPg, nbPg, curpage, step, mode) return _index(req, **args) # Answer to both /submit/ and /submit __call__ = index def errorMsg(title, req, c=None, ln=CFG_SITE_LANG): # load the right message language _ = gettext_set_language(ln) if c is None: c = CFG_SITE_NAME_INTL.get(ln, CFG_SITE_NAME) return page(title = _("Error"), body = create_error_box(req, title=str(title), verbose=0, ln=ln), description="%s - Internal Error" % c, keywords="%s, Internal Error" % c, uid = getUid(req), language=ln, req=req, navmenuid='submit') def warningMsg(title, req, c=None, ln=CFG_SITE_LANG): # load the right message language _ = gettext_set_language(ln) if c is None: c = CFG_SITE_NAME_INTL.get(ln, CFG_SITE_NAME) return page(title = _("Warning"), body = title, description="%s - Internal Error" % c, keywords="%s, Internal Error" % c, uid = getUid(req), language=ln, req=req, navmenuid='submit') def print_warning(msg, type='', prologue='<br />', epilogue='<br />'): """Prints warning message and flushes output.""" if msg: return websubmit_templates.tmpl_print_warning( msg = msg, type = type, prologue = prologue, epilogue = epilogue, ) else: return '' def retrieve_most_recent_attached_file(file_path): """ Retrieve the latest file that has been uploaded with the FCKeditor. This is the only way to retrieve files that the FCKeditor has renamed after the upload. Eg: 'prefix/image.jpg' was uploaded but did already exist. FCKeditor silently renamed it to 'prefix/image(1).jpg': >>> retrieve_most_recent_attached_file('prefix/image.jpg') 'prefix/image(1).jpg' """ (base_path, filename) = os.path.split(file_path) base_name = os.path.splitext(filename)[0] file_ext = os.path.splitext(filename)[1][1:] most_recent_filename = filename i = 0 while True: i += 1 possible_filename = "%s(%d).%s" % \ (base_name, i, file_ext) if os.path.exists(base_path + os.sep + possible_filename): most_recent_filename = possible_filename else: break return os.path.join(base_path, most_recent_filename)