diff --git a/larpe/tags/release-1.1.1/AUTHORS b/larpe/tags/release-1.1.1/AUTHORS
new file mode 100644
index 0000000..b0adfde
--- /dev/null
+++ b/larpe/tags/release-1.1.1/AUTHORS
@@ -0,0 +1,5 @@
+Damien Laniel
+
+Artwork and administrave interface design taken from DotClear, version 1.2 and
+2.0, released under the GNU General Public License; and GTK+, version 2.8,
+released under the GNU Lesser General Public License.
diff --git a/larpe/tags/release-1.1.1/COPYING b/larpe/tags/release-1.1.1/COPYING
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/larpe/tags/release-1.1.1/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/larpe/tags/release-1.1.1/MANIFEST.in b/larpe/tags/release-1.1.1/MANIFEST.in
new file mode 100644
index 0000000..06c2ffc
--- /dev/null
+++ b/larpe/tags/release-1.1.1/MANIFEST.in
@@ -0,0 +1,13 @@
+include Makefile
+include setup.py
+include larpectl
+include apache2-vhost-larpe
+include apache2.conf
+include larpe-reload-apache2-script
+include larpe-reload-apache2.c
+include README COPYING MANIFEST.in MANIFEST NEWS AUTHORS
+recursive-include larpe *.py *.ptl
+recursive-include data *
+recursive-include root *
+recursive-include po *.po *.pot Makefile
+recursive-include doc *.rst Makefile *.png *.sty custom.tex *.py *.sh *.css
diff --git a/larpe/tags/release-1.1.1/Makefile b/larpe/tags/release-1.1.1/Makefile
new file mode 100644
index 0000000..b0be307
--- /dev/null
+++ b/larpe/tags/release-1.1.1/Makefile
@@ -0,0 +1,45 @@
+prefix = /usr
+config_prefix = /var
+config_dir = $(config_prefix)/lib/larpe
+
+INSTALL = /usr/bin/install -c
+PYTHON = /usr/bin/python
+
+LARPE_VERSION = 0.2.9
+
+larpe-reload-apache2: larpe-reload-apache2.c
+
+install: larpe-reload-apache2
+ rm -rf build
+ $(MAKE) -C po install
+ $(PYTHON) setup.py install --root "$(DESTDIR)/" --prefix "$(prefix)" --no-compile
+ $(INSTALL) -d $(DESTDIR)$(prefix)/sbin/
+ $(INSTALL) larpectl $(DESTDIR)$(prefix)/sbin/
+ $(INSTALL) -m 4550 larpe-reload-apache2 $(DESTDIR)$(prefix)/sbin/
+ $(INSTALL) -d $(DESTDIR)/etc/larpe
+ $(INSTALL) -m 644 conf/apache2-vhost-larpe-common $(DESTDIR)/etc/larpe
+ $(INSTALL) -d $(DESTDIR)$(config_dir)
+ $(INSTALL) -d $(DESTDIR)$(config_dir)/vhosts.d
+ $(INSTALL) -d $(DESTDIR)$(config_dir)/vhost-locations.d
+ $(INSTALL) -d $(DESTDIR)$(config_dir)/vhosts.d.disabled
+ $(INSTALL) -d $(DESTDIR)$(config_dir)/vhost-locations.d.disabled
+
+uninstall:
+ $(MAKE) -C po uninstall
+ -rm -f $(DESTDIR)$(prefix)/sbin/larpe-reload-apache2
+ -rm -f $(DESTDIR)$(prefix)/sbin/larpe-reload-apache2-script
+ -rm -f $(DESTDIR)$(prefix)/sbin/larpectl
+ -rm -rf $(DESTDIR)$(prefix)/share/larpe/
+ @echo
+ @echo "Depending on your Python version, you will have to remove manually the files in /usr/lib/python(version)/site-packages/larpe/"
+
+clean:
+ $(MAKE) -C po clean
+ $(MAKE) -C doc clean
+ -$(PYTHON) setup.py clean
+ -rm -f larpe-reload-apache2
+
+dist: clean
+ tar czf dist/larpe-$(LARPE_VERSION).tar.gz -C .. --transform s/trunk/larpe-$(LARPE_VERSION)/ --exclude-from=exclude_from_dist trunk
+
+.PHONY: clean dist
diff --git a/larpe/tags/release-1.1.1/NEWS b/larpe/tags/release-1.1.1/NEWS
new file mode 100644
index 0000000..5e08d64
--- /dev/null
+++ b/larpe/tags/release-1.1.1/NEWS
@@ -0,0 +1,17 @@
+NEWS
+====
+
+Version 1.0
+-----------
+
+- Adds Liberty Alliance to some sites without modying sites code
+- SAML 2.0 and ID-FF 1.2 support (authentication and logout)
+- Form prefilling with ID-WSF 2.0
+- Configuration assistant (wizard-like) for configuring new sites
+- Fully tested for several sites with very different behaviours
+- Plugin system to handle specific behaviour of some sites
+- Automatic Apache 2 configuration
+- Automatic creation of Apache python filters to transform authentication boxes on the sites
+- Support for proxies
+- Logging and debug options
+- Packages for Debian and Fedora (and children of both) distributions
diff --git a/larpe/tags/release-1.1.1/README b/larpe/tags/release-1.1.1/README
new file mode 100644
index 0000000..f135f76
--- /dev/null
+++ b/larpe/tags/release-1.1.1/README
@@ -0,0 +1,32 @@
+Larpe - Liberty Alliance Reverse Proxy
+======================================
+
+Description
+-----------
+
+Larpe is a Liberty Alliance Reverse Proxy. It allows any service provider (that
+is a website) to use Liberty Alliance features (Identity federation, Single
+Sign On and Single Sign Logout) without changing the code of the service
+provider itself. It uses the Lasso library which is certified by the Liberty
+Alliance consortium.
+
+
+Documentation
+-------------
+
+* README, as you are doing;
+
+* doc/en/ for English documentation, published as HTML on
+ http://larpe.labs.libre-entreprise.org/doc/en/larpe-admin.html
+
+
+Copyright
+---------
+
+Larpe is copyrighted by Entr'ouvert and is licensed under the GNU General
+Public Licence. Artwork and administrative design are from DotClear and
+released under the GNU General Public License by Olivier Meunier and others.
+Some artwork comes from GTK+ (LGPL).
+
+Read the COPYING file for the complete license text. Read the AUTHORS file for
+additional credits.
diff --git a/larpe/tags/release-1.1.1/TODO b/larpe/tags/release-1.1.1/TODO
new file mode 100644
index 0000000..5564cc8
--- /dev/null
+++ b/larpe/tags/release-1.1.1/TODO
@@ -0,0 +1,101 @@
+- Tests
+ - egroupware
+ - http://labs.libre-entreprise.org/
+ - logs.entrouvert.org
+
+====== Roadmap de Larpe ======
+
+===== 0.2 =====
+
+ * Vérifier la compatibilité avec egroupware
+ * Mettre le vhosts générés dans /var/lib/larpe/vhosts.d et mettre un include /var/lib/larpe/vhosts.d/* dans la conf générale
+ * Supprimer debconf
+ * Vérification des formulaires de configuration d'hôtes
+ * Tests de valeurs erronés diverses
+ * Erreur si on donne un label qui existe deja
+ * Ne plus inclure le binaire larpe-reload-apache2 dans les sources
+ * Corriger les avertissements debian
+ * Ne pas demander la clé publique de l'idp
+ * Compléter les traductions
+ * Ajouter la possibilité de changer la langue dans l'interface d'administration
+ * Mettre à jour la documentation
+ * Ajouter un chapitre sur les sites testés et leurs options de configuration particulières
+ * Traduire la documentation en français
+
+===== 0.3 =====
+
+ * Implémenter le SLO en SOAP
+ * Supprimer un /liberty/ des urls
+ * Voir comment activer le SSLProxyEngine quand on utilise un sous répertoire
+ * Faire un site web pour présenter Larpe
+ * Ajouter la possibilité d'envoyer les exceptions par courriel à l'administrateur
+ * Améliorer la journalisation des accès et des erreurs
+
+===== 1.0 =====
+
+ * Support de SAML 2.0
+ * Implémenter l'accès à un site nécessitant une authentification préalable avant tout accès
+ * Choix de cette fonctionnalité par une option de configuration par site
+ * Lors de la création d'un site, choix d'un moteur de site connu (mediawiki, squirrelmail, ...) qui pré-remplirait un ensemble d'options nécessaire à ce moteur
+ * Documentation technique pour les développeurs ?
+
+===== Non classés =====
+
+ * Support des sites qui ont une authentification HTTP (à priori, nécessite de charger toute la configuration de larpe dans le filtre python d'apache)
+ * Création de nouveaux comptes pour les sites, avec des jetons (déjà implémenté en partie ; est-ce utile ?)
+
+Fait
+====
+
+- Serveur python principal
+ - Fonctionnalités liberty
+ - SSO (depuis le sp et depuis l'idp)
+ - Fédération
+ - SLO (depuis le sp et depuis l'idp)
+ - Défédération (depuis l'idp) en SOAP et redirect
+ - Support https
+ - Possibilité d'utiliser toutes les combinaisons de sous domaines et de sous répertoires
+ - RP par vhost (appli1.example.com, rp de appli1.interne)
+ - RP par repertoire (www.example.com/appli1, rp de appl1.interne)
+ - Récupère la configuration de l'IP des vhosts
+
+- Administration
+ - Authentification liberty sur l'admin
+ - Créer de nouveaux sites (+ modifier, supprimer)
+ - Écrire les vhosts correspondants
+ - Rechargement de la configuration d'apache
+ - Script + wrapper en C suid root
+ - Gestion d'utilisateurs pour administrer le RP (Authentification http)
+ - Gestion des traductions
+
+- Filtre Python branché en sortie sur Apache à la suite du filtre de réécriture html (proxy_html)
+ - Générique
+ - Personalisable par site pour une meilleure intégration dans les pages
+
+- Sites testés
+ - Dotclear
+ - Dacode
+ - linuxfr.org
+ - Sympa
+ - listes.entrouvert.com
+ - listes.libre-entreprise.org
+ - Mediawiki
+ - all4dev.libre-entreprise.org
+ - www.libre-entreprise.org
+ - www.besancon.com
+ - Egroupware
+ - quintine.entrouvert.org/egroupware/
+ - squirrelmail
+ - Concerto Espace-famille
+ - Ciril Net RH
+ - Agirhe
+
+- Documentation
+
+- Paquets Debian
+ - Debconf pour demander le nom de domaine et le courriel de l'admin, ainsi que le compte administrateur
+
+- Installation sur lupin
+
+- Batterie de tests de non-regression
+
diff --git a/larpe/tags/release-1.1.1/conf/apache2-vhost-larpe b/larpe/tags/release-1.1.1/conf/apache2-vhost-larpe
new file mode 100644
index 0000000..d5ff56b
--- /dev/null
+++ b/larpe/tags/release-1.1.1/conf/apache2-vhost-larpe
@@ -0,0 +1,12 @@
+
+ ServerName localhost
+ ServerAdmin root@localhost
+
+ include /etc/larpe/apache2-vhost-larpe-common
+ include /var/lib/larpe/vhost-locations.d
+
+ CustomLog /var/log/apache2/larpe-access.log combined
+ ErrorLog /var/log/apache2/larpe-error.log
+
+
+include /var/lib/larpe/vhosts.d
diff --git a/larpe/tags/release-1.1.1/conf/apache2-vhost-larpe-common b/larpe/tags/release-1.1.1/conf/apache2-vhost-larpe-common
new file mode 100644
index 0000000..3e3d86d
--- /dev/null
+++ b/larpe/tags/release-1.1.1/conf/apache2-vhost-larpe-common
@@ -0,0 +1,19 @@
+# Static files
+DocumentRoot /usr/share/larpe/web/
+
+# Python application
+SCGIMount / 127.0.0.1:3007
+
+# Static files for larpe
+
+ ProxyPass !
+ SCGIHandler off
+
+
+# Larpe python application
+
+ ProxyPass !
+
+
+# No gzip compression
+RequestHeader unset Accept-Encoding
diff --git a/larpe/tags/release-1.1.1/conf/filters/output_ciril_net_rh.py b/larpe/tags/release-1.1.1/conf/filters/output_ciril_net_rh.py
new file mode 100644
index 0000000..e61fe32
--- /dev/null
+++ b/larpe/tags/release-1.1.1/conf/filters/output_ciril_net_rh.py
@@ -0,0 +1,63 @@
+import re
+import os
+import pickle
+
+from larpe import sessions
+from mod_python import Cookie
+
+def is_auth_ok(req):
+ """ Test if you are authenticate on the Larpe server """
+ cookies = Cookie.get_cookies(req, Cookie.MarshalCookie, secret='secret007')
+ sessions_dir = os.path.join("%(larpe_dir)s", "sessions")
+ for name, cookie in cookies.iteritems():
+ value = cookie.value.replace('"', '')
+ if "larpe-" in name and value in os.listdir(sessions_dir):
+ try:
+ file = open(os.path.join(sessions_dir, value), "rb")
+ session = pickle.load(file)
+ if not session.users or not session.id:
+ return False
+ return True
+ except Exception, err:
+ return False
+ return False
+
+
+def filter_page(filter, page):
+ r = re.compile(r"""(D.*?connexion)""")
+ page = r.sub(r"""\1%(logout_url)s\2""", page)
+ return page
+
+def outputfilter(filter):
+ """ Apache called this function by default """
+ if not re.search("^/liberty/.*", filter.req.uri) and not is_auth_ok(filter.req):
+ filter.write(' ')
+ filter.close()
+ return
+
+ if filter.req.content_type is not None:
+ is_html = re.search('text/html', filter.req.content_type)
+ if filter.req.content_type is None or not is_html:
+ filter.pass_on()
+ else:
+ if not hasattr(filter.req, 'temp_doc'):
+ # Create a new attribute to hold the document
+ filter.req.temp_doc = []
+ # If content-length ended up wrong, Gecko browsers truncated data
+ if 'Content-Length' in filter.req.headers_out:
+ del filter.req.headers_out['Content-Length']
+
+ temp_doc = filter.req.temp_doc
+ s = filter.read()
+ # Could get '' at any point, but only get None at end
+ while s:
+ temp_doc.append(s)
+ s = filter.read()
+
+ # The end
+ if s is None:
+ page = ''.join(temp_doc)
+ page = filter_page(filter, page)
+ filter.write(page)
+ filter.close()
+
diff --git a/larpe/tags/release-1.1.1/conf/filters/output_replace_form.py b/larpe/tags/release-1.1.1/conf/filters/output_replace_form.py
new file mode 100644
index 0000000..d8964a5
--- /dev/null
+++ b/larpe/tags/release-1.1.1/conf/filters/output_replace_form.py
@@ -0,0 +1,35 @@
+import re
+
+def filter_page(filter, page):
+ current_form = re.compile('', re.DOTALL)
+ page = current_form.sub('', page)
+ return page
+
+def outputfilter(filter):
+ # Only filter html code
+ if filter.req.content_type is not None:
+ is_html = re.search('text/html', filter.req.content_type)
+ if filter.req.content_type is None or not is_html:
+ filter.pass_on()
+ else:
+ if not hasattr(filter.req, 'temp_doc'):
+ # Create a new attribute to hold the document
+ filter.req.temp_doc = []
+ # If content-length ended up wrong, Gecko browsers truncated data
+ if 'Content-Length' in filter.req.headers_out:
+ del filter.req.headers_out['Content-Length']
+
+ temp_doc = filter.req.temp_doc
+ s = filter.read()
+ # Could get '' at any point, but only get None at end
+ while s:
+ temp_doc.append(s)
+ s = filter.read()
+
+ # The end
+ if s is None:
+ page = ''.join(temp_doc)
+ page = filter_page(filter, page)
+ filter.write(page)
+ filter.close()
+
diff --git a/larpe/tags/release-1.1.1/debian/changelog b/larpe/tags/release-1.1.1/debian/changelog
new file mode 100644
index 0000000..74aab13
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/changelog
@@ -0,0 +1,89 @@
+larpe (1.1.1-1) unstable; urgency=low
+
+ * Removing a useless import
+ * Change Debian maintainer
+ * Change architecture from any to all
+ * Using pysupport instead of pycentral
+ * Update Debian package dependencies
+
+ -- Jerome Schneider Mon, 19 Jul 2010 16:18:28 +0200
+
+larpe (1.1-1) unstable; urgency=low
+
+ * New release
+ - Rewrite filter management
+ - Filters are now customisable
+ - Improve Ciril module
+ - Improve plugins management
+ - Support multi filters
+ - Fix SAML 2 logout
+ - Fix site authentification plugins management
+ - Fix sessions management
+ - Code cleaning
+
+ -- Jerome Schneider Mon, 19 Jul 2010 11:52:47 +0200
+
+larpe (1.0-1) unstable; urgency=low
+
+ * New release
+ - SAML 2.0 and ID-FF 1.2 support (authentication and logout)
+ - Form prefilling with ID-WSF 2.0
+ - Configuration assistant (wizard-like) for configuring new sites
+ - Fully tested for several sites with very different behaviours
+ - Plugin system to handle specific behaviour of some sites
+ - Automatic Apache 2 configuration
+ - Automatic creation of Apache python filters to transform authentication
+ boxes on the sites
+ - Support for proxies
+ - Logging and debug options
+
+ -- Damien Laniel Mon, 09 Mar 2009 11:19:49 +0100
+
+larpe (0.2.1-1) unstable; urgency=low
+
+ * New release
+
+ -- Damien Laniel Wed, 20 Jun 2007 15:43:16 +0200
+
+larpe (0.2.0-1) unstable; urgency=low
+
+ * New release
+
+ -- Damien Laniel Tue, 30 Jan 2007 18:07:04 +0100
+
+larpe (0.1.1-2) unstable; urgency=low
+
+ * Use python2.4
+
+ -- Damien Laniel Tue, 19 Dec 2006 17:21:05 +0100
+
+larpe (0.1.1-1) unstable; urgency=low
+
+ * New release
+
+ -- Damien Laniel Thu, 5 Oct 2006 11:47:53 +0200
+
+larpe (0.1.0-1) unstable; urgency=low
+
+ * New release
+
+ -- Damien Laniel Wed, 4 Oct 2006 10:19:26 +0200
+
+larpe (0.0.4-1) unstable; urgency=low
+
+ * New version, many improvements, more compatible sites, some bug fixes
+
+ -- Damien Laniel Tue, 3 Oct 2006 20:44:06 +0200
+
+larpe (0.0.3-1) unstable; urgency=low
+
+ * New version, many improvements, more compatible sites, some bug fixes
+
+ -- Damien Laniel Mon, 25 Sep 2006 11:11:36 +0200
+
+larpe (0.0.2-1) unstable; urgency=low
+
+ * Initial package.
+
+ -- Damien Laniel Fri, 08 Sep 2006 16:00:00 +0200
+
diff --git a/larpe/tags/release-1.1.1/debian/compat b/larpe/tags/release-1.1.1/debian/compat
new file mode 100644
index 0000000..7ed6ff8
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/compat
@@ -0,0 +1 @@
+5
diff --git a/larpe/tags/release-1.1.1/debian/config b/larpe/tags/release-1.1.1/debian/config
new file mode 100755
index 0000000..9543f2d
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/config
@@ -0,0 +1,24 @@
+#!/bin/sh -e
+
+# Source debconf library.
+. /usr/share/debconf/confmodule
+
+# Hostname
+#db_input high larpe/hostname || true
+#db_go
+
+# Administrator email address
+#db_input medium larpe/admin_email || true
+#db_go
+
+# Enable this vhost
+#db_input high larpe/enable_vhost || true
+#db_go
+
+# Administrator login
+#db_input high larpe/admin_username || true
+#db_go
+
+# Administrator password
+#db_input high larpe/admin_password || true
+#db_go
diff --git a/larpe/tags/release-1.1.1/debian/control b/larpe/tags/release-1.1.1/debian/control
new file mode 100644
index 0000000..812ff5d
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/control
@@ -0,0 +1,16 @@
+Source: larpe
+Section: web
+Priority: optional
+Maintainer: Jérôme Schneider
+Build-Depends: debhelper (>= 5.0.37.2), python, python-support (>= 0.4), gettext, python-quixote (>= 2.5)
+Standards-Version: 3.8.0
+
+Package: larpe
+Architecture: all
+Depends: ${shlibs:Depends}, ${python:Depends}, python-quixote (>= 2.5), python-lasso (>= 2.2.1), python-scgi, python-libxml2, apache2, libapache2-mod-scgi, libapache2-mod-python, libapache2-mod-proxy-html
+XB-Python-Version: ${python:Versions}
+Description: Liberty Alliance Reverse Proxy
+ Larpe allows any service provider (that is a website) to use Liberty Alliance
+ identity management and Single Sign On features without changing the code of
+ the service provider itself.
+ .
diff --git a/larpe/tags/release-1.1.1/debian/copyright b/larpe/tags/release-1.1.1/debian/copyright
new file mode 100644
index 0000000..c81e494
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/copyright
@@ -0,0 +1,27 @@
+This package was debianized by Damien Laniel on
+Fri, 08 Sep 2006 16:00:00 +0200.
+
+Upstream Author: Damien Laniel
+
+Copyright (c) 2005 Entr'ouvert;
+copyright (c) 2003-2005 dotclear for some graphics.
+
+License is GNU GPL v2 or later plus OpenSSL exception clause.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+On Debian GNU/Linux systems, the complete text of the GNU General Public
+License can be found in `/usr/share/common-licenses/GPL'.
+
diff --git a/larpe/tags/release-1.1.1/debian/dirs b/larpe/tags/release-1.1.1/debian/dirs
new file mode 100644
index 0000000..cc80198
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/dirs
@@ -0,0 +1,4 @@
+etc/apache2/sites-available
+etc/larpe
+usr/sbin
+var/lib/larpe
diff --git a/larpe/tags/release-1.1.1/debian/docs b/larpe/tags/release-1.1.1/debian/docs
new file mode 100644
index 0000000..55bc0a6
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/docs
@@ -0,0 +1,2 @@
+README
+AUTHORS
diff --git a/larpe/tags/release-1.1.1/debian/init b/larpe/tags/release-1.1.1/debian/init
new file mode 100644
index 0000000..edca4da
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/init
@@ -0,0 +1,82 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: larpe
+# Required-Start: $local_fs $network
+# Required-Stop: $local_fs $network
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Start Larpe Liberty Alliance reverse proxy
+# Description: Start Larpe Liberty Alliance reverse proxy
+### END INIT INFO
+
+set -e
+
+# Gracefully exit if the package has been removed.
+test -x $DAEMON || exit 0
+
+# Source function library
+. /lib/lsb/init-functions
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DESC="larpe"
+NAME=larpe
+DAEMON=/usr/sbin/larpectl
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+
+# Read config file if it is present.
+if [ -r /etc/default/$NAME ]
+then
+ . /etc/default/$NAME
+fi
+
+#
+# Function that starts the daemon/service.
+#
+d_start() {
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --oknodo \
+ --chuid www-data:www-data --make-pidfile --background --exec $DAEMON -- start $OPTIONS
+}
+
+#
+# Function that stops the daemon/service.
+#
+d_stop() {
+ start-stop-daemon --stop --quiet --pidfile $PIDFILE --oknodo
+ rm -f $PIDFILE
+}
+
+case "$1" in
+ start)
+ log_begin_msg "Starting $DESC: $NAME"
+ d_start
+ log_end_msg $?
+ ;;
+
+ stop)
+ log_begin_msg "Stopping $DESC: $NAME"
+ d_stop
+ log_end_msg $?
+ ;;
+
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented, move the "force-reload"
+ # option to the "reload" entry above. If not, "force-reload" is
+ # just the same as "restart".
+ #
+ log_begin_msg "Restarting $DESC: $NAME"
+ d_stop
+ sleep 1
+ d_start
+ log_end_msg $?
+ ;;
+
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/larpe/tags/release-1.1.1/debian/larpe-reload-apache2-script b/larpe/tags/release-1.1.1/debian/larpe-reload-apache2-script
new file mode 100755
index 0000000..5431d0d
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/larpe-reload-apache2-script
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+/etc/init.d/apache2 reload
diff --git a/larpe/tags/release-1.1.1/debian/postinst b/larpe/tags/release-1.1.1/debian/postinst
new file mode 100644
index 0000000..6aa61f2
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/postinst
@@ -0,0 +1,66 @@
+#! /bin/sh
+# postinst script for larpe
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * `configure'
+# * `abort-upgrade'
+# * `abort-remove' `in-favour'
+#
+# * `abort-deconfigure' `in-favour'
+# `removing'
+#
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+#
+# quoting from the policy:
+# Any necessary prompting should almost always be confined to the
+# post-installation script, and should be protected with a conditional
+# so that unnecessary prompting doesn't happen if a package's
+# installation fails and the `postinst' is called with `abort-upgrade',
+# `abort-remove' or `abort-deconfigure'.
+
+PACKAGE=larpe
+VERSION=2.4
+LIB="/usr/lib/python$VERSION"
+DIRLIST="/usr/share/pycentral/larpe/site-packages/larpe/"
+
+case "$1" in
+ configure|abort-upgrade|abort-remove|abort-deconfigure)
+ for i in $DIRLIST ; do
+ /usr/bin/python$VERSION -O $LIB/compileall.py -q $i
+ /usr/bin/python$VERSION $LIB/compileall.py -q $i
+ done
+
+ # Load Apache 2 modules
+ for module in "proxy" "rewrite" "headers" "proxy_http"; do
+ a2enmod ${module} > /dev/null || true
+ done
+
+ # Restart Apache 2
+ set +e
+ if [ -x /usr/sbin/invoke-rc.d ]; then
+ invoke-rc.d apache2 restart || true
+ else
+ /etc/init.d/apache2 restart || true
+ fi
+ set -e
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/larpe/tags/release-1.1.1/debian/prerm b/larpe/tags/release-1.1.1/debian/prerm
new file mode 100644
index 0000000..525d726
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/prerm
@@ -0,0 +1,41 @@
+#! /bin/sh
+# prerm script for larpe
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * `remove'
+# * `upgrade'
+# * `failed-upgrade'
+# * `remove' `in-favour'
+# * `deconfigure' `in-favour'
+# `removing'
+#
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+PACKAGE=larpe
+
+case "$1" in
+ remove|upgrade|deconfigure)
+ dpkg --listfiles $PACKAGE |
+ awk '$0~/\.py$/ {print $0"c\n" $0"o"}' |
+ xargs rm -f >&2
+ ;;
+ failed-upgrade)
+ ;;
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/larpe/tags/release-1.1.1/debian/pycompat b/larpe/tags/release-1.1.1/debian/pycompat
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/pycompat
@@ -0,0 +1 @@
+2
diff --git a/larpe/tags/release-1.1.1/debian/rules b/larpe/tags/release-1.1.1/debian/rules
new file mode 100755
index 0000000..f898b01
--- /dev/null
+++ b/larpe/tags/release-1.1.1/debian/rules
@@ -0,0 +1,74 @@
+#!/usr/bin/make -f
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+PYTHON=/usr/bin/python
+
+LARPE_USER = www-data
+LARPE_GROUP = www-data
+
+ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS)))
+ CFLAGS += -g
+endif
+ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
+ INSTALL_PROGRAM += -s
+endif
+
+build: build-stamp
+
+build-stamp:
+ dh_testdir
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp
+
+ make clean
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ make install prefix=/usr DESTDIR=$(CURDIR)/debian/larpe
+ # Apache Vhost
+ dh_install conf/apache2-vhost-larpe etc/apache2/sites-available
+ # Apache reload script
+ dh_install debian/larpe-reload-apache2-script usr/sbin
+
+ # Give files ownership to Larpe user and group
+ chown -R $(LARPE_USER):$(LARPE_GROUP) $(CURDIR)/debian/larpe/usr/share/larpe/
+ chown -R $(LARPE_USER):$(LARPE_GROUP) $(CURDIR)/debian/larpe/var/lib/larpe/
+ chgrp $(LARPE_GROUP) $(CURDIR)/debian/larpe/usr/sbin/larpe-reload-apache2
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+ dh_installdocs
+ dh_installinit
+ dh_installchangelogs
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms -X /var/lib/larpe -X /usr/sbin/larpe-reload-apache2
+ dh_pysupport
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install
diff --git a/larpe/tags/release-1.1.1/doc/Makefile b/larpe/tags/release-1.1.1/doc/Makefile
new file mode 100644
index 0000000..9a27348
--- /dev/null
+++ b/larpe/tags/release-1.1.1/doc/Makefile
@@ -0,0 +1,8 @@
+all:
+ $(MAKE) -C en
+
+clean:
+ $(MAKE) -C en clean
+
+.PHONY: clean
+
diff --git a/larpe/tags/release-1.1.1/doc/en/Makefile b/larpe/tags/release-1.1.1/doc/en/Makefile
new file mode 100644
index 0000000..295ee43
--- /dev/null
+++ b/larpe/tags/release-1.1.1/doc/en/Makefile
@@ -0,0 +1,35 @@
+RST2HTML = rst2html
+RST2LATEX = ../scripts/rst2latex.py
+PDFLATEX = pdflatex
+RM = rm -f
+
+all: larpe-admin.pdf larpe-admin.html
+
+%.html: %.rst
+ $(RST2HTML) --stylesheet=default.css --link-stylesheet --language=en $? > $@
+
+figures-no-alpha-stamp:
+ -$(RM) -r figures-no-alpha/
+ mkdir figures-no-alpha/
+ for F in figures/*.png; do \
+ ../scripts/removealpha.sh $$F figures-no-alpha/`basename $$F`; \
+ done
+ touch figures-no-alpha-stamp
+
+%.tex: %.rst #figures-no-alpha-stamp
+ cat $? | sed -e 's/figures\//figures-no-alpha\//' \
+ -e 's/ ::$$/ : ::/g' \
+ -e 's/.. section-numbering:://' | $(RST2LATEX) --language=en > $@
+
+%.pdf: %.tex custom.tex
+ $(PDFLATEX) $?
+ logfile=`echo "$@" |sed -r "s/(.*)....$$/\\1/"`.log; while [ -f "$$logfile" -a -n "`grep "Rerun to get cross-references right" $$logfile`" ]; do $(PDFLATEX) $< ; done
+
+clean:
+ -$(RM) *.aux *.toc *.log *.out
+ -$(RM) larpe-admin.pdf
+ -$(RM) larpe-admin.tex
+ -$(RM) larpe-admin.html
+ -$(RM) -r figures-no-alpha figures-no-alpha-stamp
+
+.PHONY: all clean
diff --git a/larpe/tags/release-1.1.1/doc/en/custom.tex b/larpe/tags/release-1.1.1/doc/en/custom.tex
new file mode 100644
index 0000000..cf42eb7
--- /dev/null
+++ b/larpe/tags/release-1.1.1/doc/en/custom.tex
@@ -0,0 +1,45 @@
+\usepackage{float,fancyhdr,lscape,sectsty,colortbl,color,lastpage,setspace}
+\usepackage[perpage,bottom]{footmisc}
+\usepackage[hang]{caption2}
+\usepackage{marvosym}
+
+\usepackage{float,url,listings,tocbibind,fancyhdr,calc,placeins}
+
+\usepackage{palatino}
+\usepackage[Glenn]{fncychap}
+
+\pagestyle{fancy}
+\fancyhead{}
+\fancyfoot{}
+\fancyhead[L]{Authentic}
+\fancyhead[R]{Administrator Guide}
+\fancyfoot[C]{Page \thepage}
+\addtolength{\headheight}{1.6pt}
+
+\setlength\parindent{0pt}
+\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
+\setlength\abovecaptionskip{0.1ex}
+
+\makeatletter
+\renewcommand{\maketitle}{\begin{titlepage}%
+ \let\footnotesize\small
+ \let\footnoterule\relax
+ \parindent \z@
+ \reset@font
+ \null\vfil
+ \begin{flushleft}
+ \huge \@title
+ \end{flushleft}
+ \par
+ \hrule height 1pt
+ \par
+ \begin{flushright}
+ \LARGE \@author \par
+ \end{flushright}
+ \vskip 60\p@
+ \vfil\null
+ \end{titlepage}%
+ \setcounter{footnote}{0}%
+}
+\makeatother
+
diff --git a/larpe/tags/release-1.1.1/doc/en/default.css b/larpe/tags/release-1.1.1/doc/en/default.css
new file mode 100644
index 0000000..d198a16
--- /dev/null
+++ b/larpe/tags/release-1.1.1/doc/en/default.css
@@ -0,0 +1,143 @@
+body {
+ font-family: sans-serif;
+}
+
+
+h1 a, h2 a, h3 a, h4 a {
+ text-decoration: inherit;
+ color: inherit;
+}
+
+pre.literal-block {
+ background: #eee;
+ border: 1px inset black;
+ padding: 2px;
+ margin: auto 10px;
+ overflow: auto;
+}
+
+h1.title {
+ text-align: center;
+ background: #eef;
+ border: 1px solid #aaf;
+ letter-spacing: 1px;
+}
+
+div.section {
+ margin-bottom: 2em;
+}
+
+div.section h1 {
+ padding: 0 15px;
+ background: #eef;
+ border: 1px solid #aaf;
+}
+
+div.section h2 {
+ padding: 0 15px;
+ background: #eef;
+ border: 1px solid #aaf;
+}
+
+div.document {
+ margin-top: 1em;
+ border-top: 1px solid #aaf;
+ border-bottom: 1px solid #aaf;
+}
+
+div.section p,
+div.section ul {
+ text-align: justify;
+}
+
+div.contents {
+ float: right;
+ border: 1px solid black;
+ margin: 1em;
+ background: #eef;
+ max-width: 33%;
+}
+
+div#building-liberty-services-with-lasso div#table-of-contents {
+ max-width: inherit;
+ float: none;
+ background: white url(lasso.png) bottom right no-repeat;
+}
+
+div.contents ul {
+ padding-left: 1em;
+ list-style: none;
+}
+
+div.contents li {
+ padding-bottom: 2px;
+}
+
+div.contents p {
+ background: #ddf;
+ text-align: center;
+ border-bottom: 1px solid black;
+ margin: 0;
+}
+
+th.docinfo-name {
+ text-align: right;
+ padding-right: 0.5em;
+}
+
+dd {
+ margin-bottom: 1ex;
+}
+
+table.table {
+ margin: 1ex 0;
+ border-spacing: 0px;
+}
+
+
+table.table th {
+ padding: 0px 1ex;
+ background: #eef;
+ font-weight: normal;
+}
+
+
+table.table td {
+ padding: 0 0.5ex;
+}
+
+div.note, div.warning {
+ padding: 0.3ex;
+ padding-left: 60px;
+ min-height: 50px;
+ margin: 1ex 1em;
+}
+
+div.note {
+ background: #ffa url(note.png) top left no-repeat;
+ border: 1px solid #fd8;
+}
+
+div.warning {
+ background: #ffd url(warning.png) top left no-repeat;
+}
+
+p.admonition-title {
+ font-weight: bold;
+ display: inline;
+ display: none;
+ padding-right: 1em;
+}
+
+div.figure {
+ margin: 0 auto;
+ width: 70%;
+ min-width: 800px;
+ text-align: center;
+}
+
+div.figure p.caption {
+ font-style: italic;
+ margin: 1ex 0 2em 0;
+ text-align: center;
+}
diff --git a/larpe/tags/release-1.1.1/doc/en/fncychap.sty b/larpe/tags/release-1.1.1/doc/en/fncychap.sty
new file mode 100644
index 0000000..9c4ed8d
--- /dev/null
+++ b/larpe/tags/release-1.1.1/doc/en/fncychap.sty
@@ -0,0 +1,490 @@
+%%% Copyright Ulf A. Lindgren
+%%%
+%%% Note Premission is granted to modify this file under
+%%% the condition that it is saved using another
+%%% file and package name.
+%%%
+%%% Revision 1.1 (1997)
+%%%
+%%% Jan. 8th Modified package name base date option
+%%% Jan. 22th Modified FmN and FmTi for error in book.cls
+%%% \MakeUppercase{#}->{\MakeUppercase#}
+%%% Apr. 6th Modified Lenny option to prevent undesired
+%%% skip of line.
+%%% Nov. 8th Fixed \@chapapp for AMS
+%%%
+%%% Revision 1.2 (1998)
+%%%
+%%% Feb. 11th Fixed appendix problem related to Bjarne
+%%% Aug. 11th Fixed problem related to 11pt and 12pt
+%%% suggested by Tomas Lundberg. THANKS!
+%%%
+%%% Revision 1.3 (2004)
+%%% Sep. 20th problem with frontmatter, mainmatter and
+%%% backmatter, pointed out by Lapo Mori
+%%%
+%%% Revision 1.31 (2004)
+%%% Sep. 21th problem with the Rejne definition streched text
+%%% caused ugly gaps in the vrule aligned with the title
+%%% text. Kindly pointed out to me by Hendri Adriaens
+%%%
+%%% Revision 1.32 (2005)
+%%% Jun. 23th compatibility problem with the KOMA class 'scrbook.cls'
+%%% a remedy is a redefinition of '\@schapter' in
+%%% line with that used in KOMA. The problem was pointed
+%%% out to me by Mikkel Holm Olsen
+%%%
+%%% Revision 1.33 (2005)
+%%% Aug. 9th misspelled ``TWELV'' corrected, the error was pointed
+%%% out to me by George Pearson
+%%%
+
+
+%%% Last modified Aug. 9th 2005
+
+\NeedsTeXFormat{LaTeX2e}[1995/12/01]
+\ProvidesPackage{fncychap}
+ [2004/09/21 v1.33
+ LaTeX package (Revised chapters)]
+
+%%%% DEFINITION OF Chapapp variables
+\newcommand{\CNV}{\huge\bfseries}
+\newcommand{\ChNameVar}[1]{\renewcommand{\CNV}{#1}}
+
+
+%%%% DEFINITION OF TheChapter variables
+\newcommand{\CNoV}{\huge\bfseries}
+\newcommand{\ChNumVar}[1]{\renewcommand{\CNoV}{#1}}
+
+\newif\ifUCN
+\UCNfalse
+\newif\ifLCN
+\LCNfalse
+\def\ChNameLowerCase{\LCNtrue\UCNfalse}
+\def\ChNameUpperCase{\UCNtrue\LCNfalse}
+\def\ChNameAsIs{\UCNfalse\LCNfalse}
+
+%%%%% Fix for AMSBook 971008
+
+\@ifundefined{@chapapp}{\let\@chapapp\chaptername}{}
+
+
+%%%%% Fix for Bjarne and appendix 980211
+
+\newif\ifinapp
+\inappfalse
+\renewcommand\appendix{\par
+ \setcounter{chapter}{0}%
+ \setcounter{section}{0}%
+ \inapptrue%
+ \renewcommand\@chapapp{\appendixname}%
+ \renewcommand\thechapter{\@Alph\c@chapter}}
+
+%%%%% Fix for frontmatter, mainmatter, and backmatter 040920
+
+\@ifundefined{@mainmatter}{\newif\if@mainmatter \@mainmattertrue}{}
+
+%%%%%
+
+
+
+\newcommand{\FmN}[1]{%
+\ifUCN
+ {\MakeUppercase#1}\LCNfalse
+\else
+ \ifLCN
+ {\MakeLowercase#1}\UCNfalse
+ \else #1
+ \fi
+\fi}
+
+
+%%%% DEFINITION OF Title variables
+\newcommand{\CTV}{\Huge\bfseries}
+\newcommand{\ChTitleVar}[1]{\renewcommand{\CTV}{#1}}
+
+%%%% DEFINITION OF the basic rule width
+\newlength{\RW}
+\setlength{\RW}{1pt}
+\newcommand{\ChRuleWidth}[1]{\setlength{\RW}{#1}}
+
+\newif\ifUCT
+\UCTfalse
+\newif\ifLCT
+\LCTfalse
+\def\ChTitleLowerCase{\LCTtrue\UCTfalse}
+\def\ChTitleUpperCase{\UCTtrue\LCTfalse}
+\def\ChTitleAsIs{\UCTfalse\LCTfalse}
+\newcommand{\FmTi}[1]{%
+\ifUCT
+ {\MakeUppercase#1}\LCTfalse
+\else
+ \ifLCT
+ {\MakeLowercase#1}\UCTfalse
+ \else {#1}
+ \fi
+\fi}
+
+
+
+\newlength{\mylen}
+\newlength{\myhi}
+\newlength{\px}
+\newlength{\py}
+\newlength{\pyy}
+\newlength{\pxx}
+
+
+\def\mghrulefill#1{\leavevmode\leaders\hrule\@height #1\hfill\kern\z@}
+
+\newcommand{\DOCH}{%
+ \CNV\FmN{\@chapapp}\space \CNoV\thechapter
+ \par\nobreak
+ \vskip 20\p@
+ }
+\newcommand{\DOTI}[1]{%
+ \CTV\FmTi{#1}\par\nobreak
+ \vskip 40\p@
+ }
+\newcommand{\DOTIS}[1]{%
+ \CTV\FmTi{#1}\par\nobreak
+ \vskip 40\p@
+ }
+
+%%%%%% SONNY DEF
+
+\DeclareOption{Sonny}{%
+ \ChNameVar{\Large\sf}
+ \ChNumVar{\Huge}
+ \ChTitleVar{\Large\sf}
+ \ChRuleWidth{0.5pt}
+ \ChNameUpperCase
+ \renewcommand{\DOCH}{%
+ \raggedleft
+ \CNV\FmN{\@chapapp}\space \CNoV\thechapter
+ \par\nobreak
+ \vskip 40\p@}
+ \renewcommand{\DOTI}[1]{%
+ \CTV\raggedleft\mghrulefill{\RW}\par\nobreak
+ \vskip 5\p@
+ \CTV\FmTi{#1}\par\nobreak
+ \mghrulefill{\RW}\par\nobreak
+ \vskip 40\p@}
+ \renewcommand{\DOTIS}[1]{%
+ \CTV\raggedleft\mghrulefill{\RW}\par\nobreak
+ \vskip 5\p@
+ \CTV\FmTi{#1}\par\nobreak
+ \mghrulefill{\RW}\par\nobreak
+ \vskip 40\p@}
+}
+
+%%%%%% LENNY DEF
+
+\DeclareOption{Lenny}{%
+
+ \ChNameVar{\fontsize{14}{16}\usefont{OT1}{phv}{m}{n}\selectfont}
+ \ChNumVar{\fontsize{60}{62}\usefont{OT1}{ptm}{m}{n}\selectfont}
+ \ChTitleVar{\Huge\bfseries\rm}
+ \ChRuleWidth{1pt}
+ \renewcommand{\DOCH}{%
+ \settowidth{\px}{\CNV\FmN{\@chapapp}}
+ \addtolength{\px}{2pt}
+ \settoheight{\py}{\CNV\FmN{\@chapapp}}
+ \addtolength{\py}{1pt}
+
+ \settowidth{\mylen}{\CNV\FmN{\@chapapp}\space\CNoV\thechapter}
+ \addtolength{\mylen}{1pt}
+ \settowidth{\pxx}{\CNoV\thechapter}
+ \addtolength{\pxx}{-1pt}
+
+ \settoheight{\pyy}{\CNoV\thechapter}
+ \addtolength{\pyy}{-2pt}
+ \setlength{\myhi}{\pyy}
+ \addtolength{\myhi}{-1\py}
+ \par
+ \parbox[b]{\textwidth}{%
+ \rule[\py]{\RW}{\myhi}%
+ \hskip -\RW%
+ \rule[\pyy]{\px}{\RW}%
+ \hskip -\px%
+ \raggedright%
+ \CNV\FmN{\@chapapp}\space\CNoV\thechapter%
+ \hskip1pt%
+ \mghrulefill{\RW}%
+ \rule{\RW}{\pyy}\par\nobreak%
+ \vskip -\baselineskip%
+ \vskip -\pyy%
+ \hskip \mylen%
+ \mghrulefill{\RW}\par\nobreak%
+ \vskip \pyy}%
+ \vskip 20\p@}
+
+
+ \renewcommand{\DOTI}[1]{%
+ \raggedright
+ \CTV\FmTi{#1}\par\nobreak
+ \vskip 40\p@}
+
+ \renewcommand{\DOTIS}[1]{%
+ \raggedright
+ \CTV\FmTi{#1}\par\nobreak
+ \vskip 40\p@}
+ }
+
+
+%%%%%%% GLENN DEF
+
+
+\DeclareOption{Glenn}{%
+ \ChNameVar{\bfseries\Large\sf}
+ \ChNumVar{\Huge}
+ \ChTitleVar{\bfseries\Large\rm}
+ \ChRuleWidth{1pt}
+ \ChNameUpperCase
+ \ChTitleUpperCase
+ \renewcommand{\DOCH}{%
+ \settoheight{\myhi}{\CTV\FmTi{Test}}
+ \setlength{\py}{\baselineskip}
+ \addtolength{\py}{\RW}
+ \addtolength{\py}{\myhi}
+ \setlength{\pyy}{\py}
+ \addtolength{\pyy}{-1\RW}
+
+ \raggedright
+ \CNV\FmN{\@chapapp}\space\CNoV\thechapter
+ \hskip 3pt\mghrulefill{\RW}\rule[-1\pyy]{2\RW}{\py}\par\nobreak}
+
+ \renewcommand{\DOTI}[1]{%
+ \addtolength{\pyy}{-4pt}
+ \settoheight{\myhi}{\CTV\FmTi{#1}}
+ \addtolength{\myhi}{\py}
+ \addtolength{\myhi}{-1\RW}
+ \vskip -1\pyy
+ \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 2pt
+ \raggedleft\CTV\FmTi{#1}\par\nobreak
+ \vskip 80\p@}
+
+\newlength{\backskip}
+ \renewcommand{\DOTIS}[1]{%
+% \setlength{\py}{10pt}
+% \setlength{\pyy}{\py}
+% \addtolength{\pyy}{\RW}
+% \setlength{\myhi}{\baselineskip}
+% \addtolength{\myhi}{\pyy}
+% \mghrulefill{\RW}\rule[-1\py]{2\RW}{\pyy}\par\nobreak
+% \addtolength{}{}
+%\vskip -1\baselineskip
+% \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 2pt
+% \raggedleft\CTV\FmTi{#1}\par\nobreak
+% \vskip 60\p@}
+%% Fix suggested by Tomas Lundberg
+ \setlength{\py}{25pt} % eller vad man vill
+ \setlength{\pyy}{\py}
+ \setlength{\backskip}{\py}
+ \addtolength{\backskip}{2pt}
+ \addtolength{\pyy}{\RW}
+ \setlength{\myhi}{\baselineskip}
+ \addtolength{\myhi}{\pyy}
+ \mghrulefill{\RW}\rule[-1\py]{2\RW}{\pyy}\par\nobreak
+ \vskip -1\backskip
+ \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 3pt %
+ \raggedleft\CTV\FmTi{#1}\par\nobreak
+ \vskip 40\p@}
+ }
+
+%%%%%%% CONNY DEF
+
+\DeclareOption{Conny}{%
+ \ChNameUpperCase
+ \ChTitleUpperCase
+ \ChNameVar{\centering\Huge\rm\bfseries}
+ \ChNumVar{\Huge}
+ \ChTitleVar{\centering\Huge\rm}
+ \ChRuleWidth{2pt}
+
+ \renewcommand{\DOCH}{%
+ \mghrulefill{3\RW}\par\nobreak
+ \vskip -0.5\baselineskip
+ \mghrulefill{\RW}\par\nobreak
+ \CNV\FmN{\@chapapp}\space \CNoV\thechapter
+ \par\nobreak
+ \vskip -0.5\baselineskip
+ }
+ \renewcommand{\DOTI}[1]{%
+ \mghrulefill{\RW}\par\nobreak
+ \CTV\FmTi{#1}\par\nobreak
+ \vskip 60\p@
+ }
+ \renewcommand{\DOTIS}[1]{%
+ \mghrulefill{\RW}\par\nobreak
+ \CTV\FmTi{#1}\par\nobreak
+ \vskip 60\p@
+ }
+ }
+
+%%%%%%% REJNE DEF
+
+\DeclareOption{Rejne}{%
+
+ \ChNameUpperCase
+ \ChTitleUpperCase
+ \ChNameVar{\centering\Large\rm}
+ \ChNumVar{\Huge}
+ \ChTitleVar{\centering\Huge\rm}
+ \ChRuleWidth{1pt}
+ \renewcommand{\DOCH}{%
+ \settoheight{\py}{\CNoV\thechapter}
+ \parskip=0pt plus 1pt % Set parskip to default, just in case v1.31
+ \addtolength{\py}{-1pt}
+ \CNV\FmN{\@chapapp}\par\nobreak
+ \vskip 20\p@
+ \setlength{\myhi}{2\baselineskip}
+ \setlength{\px}{\myhi}
+ \addtolength{\px}{-1\RW}
+ \rule[-1\px]{\RW}{\myhi}\mghrulefill{\RW}\hskip
+ 10pt\raisebox{-0.5\py}{\CNoV\thechapter}\hskip 10pt\mghrulefill{\RW}\rule[-1\px]{\RW}{\myhi}\par\nobreak
+ \vskip -3\p@% Added -2pt vskip to correct for streched text v1.31
+ }
+ \renewcommand{\DOTI}[1]{%
+ \setlength{\mylen}{\textwidth}
+ \parskip=0pt plus 1pt % Set parskip to default, just in case v1.31
+ \addtolength{\mylen}{-2\RW}
+ {\vrule width\RW}\parbox{\mylen}{\CTV\FmTi{#1}}{\vrule width\RW}\par\nobreak%
+ \vskip -3pt\rule{\RW}{2\baselineskip}\mghrulefill{\RW}\rule{\RW}{2\baselineskip}%
+ \vskip 60\p@% Added -2pt in vskip to correct for streched text v1.31
+ }
+ \renewcommand{\DOTIS}[1]{%
+ \setlength{\py}{\fboxrule}
+ \setlength{\fboxrule}{\RW}
+ \setlength{\mylen}{\textwidth}
+ \addtolength{\mylen}{-2\RW}
+ \fbox{\parbox{\mylen}{\vskip 2\baselineskip\CTV\FmTi{#1}\par\nobreak\vskip \baselineskip}}
+ \setlength{\fboxrule}{\py}
+ \vskip 60\p@
+ }
+ }
+
+
+%%%%%%% BJARNE DEF
+
+\DeclareOption{Bjarne}{%
+ \ChNameUpperCase
+ \ChTitleUpperCase
+ \ChNameVar{\raggedleft\normalsize\rm}
+ \ChNumVar{\raggedleft \bfseries\Large}
+ \ChTitleVar{\raggedleft \Large\rm}
+ \ChRuleWidth{1pt}
+
+
+%% Note thechapter -> c@chapter fix appendix bug
+%% Fixed misspelled 12
+
+ \newcounter{AlphaCnt}
+ \newcounter{AlphaDecCnt}
+ \newcommand{\AlphaNo}{%
+ \ifcase\number\theAlphaCnt
+ \ifnum\c@chapter=0
+ ZERO\else{}\fi
+ \or ONE\or TWO\or THREE\or FOUR\or FIVE
+ \or SIX\or SEVEN\or EIGHT\or NINE\or TEN
+ \or ELEVEN\or TWELVE\or THIRTEEN\or FOURTEEN\or FIFTEEN
+ \or SIXTEEN\or SEVENTEEN\or EIGHTEEN\or NINETEEN\fi
+}
+
+ \newcommand{\AlphaDecNo}{%
+ \setcounter{AlphaDecCnt}{0}
+ \@whilenum\number\theAlphaCnt>0\do
+ {\addtocounter{AlphaCnt}{-10}
+ \addtocounter{AlphaDecCnt}{1}}
+ \ifnum\number\theAlphaCnt=0
+ \else
+ \addtocounter{AlphaDecCnt}{-1}
+ \addtocounter{AlphaCnt}{10}
+ \fi
+
+
+ \ifcase\number\theAlphaDecCnt\or TEN\or TWENTY\or THIRTY\or
+ FORTY\or FIFTY\or SIXTY\or SEVENTY\or EIGHTY\or NINETY\fi
+ }
+ \newcommand{\TheAlphaChapter}{%
+
+ \ifinapp
+ \thechapter
+ \else
+ \setcounter{AlphaCnt}{\c@chapter}
+ \ifnum\c@chapter<20
+ \AlphaNo
+ \else
+ \AlphaDecNo\AlphaNo
+ \fi
+ \fi
+ }
+ \renewcommand{\DOCH}{%
+ \mghrulefill{\RW}\par\nobreak
+ \CNV\FmN{\@chapapp}\par\nobreak
+ \CNoV\TheAlphaChapter\par\nobreak
+ \vskip -1\baselineskip\vskip 5pt\mghrulefill{\RW}\par\nobreak
+ \vskip 20\p@
+ }
+ \renewcommand{\DOTI}[1]{%
+ \CTV\FmTi{#1}\par\nobreak
+ \vskip 40\p@
+ }
+ \renewcommand{\DOTIS}[1]{%
+ \CTV\FmTi{#1}\par\nobreak
+ \vskip 40\p@
+ }
+}
+
+\DeclareOption*{%
+ \PackageWarning{fancychapter}{unknown style option}
+ }
+
+\ProcessOptions* \relax
+
+\def\@makechapterhead#1{%
+ \vspace*{50\p@}%
+ {\parindent \z@ \raggedright \normalfont
+ \ifnum \c@secnumdepth >\m@ne
+ \if@mainmatter%%%%% Fix for frontmatter, mainmatter, and backmatter 040920
+ \DOCH
+ \fi
+ \fi
+ \interlinepenalty\@M
+ \DOTI{#1}
+ }}
+
+
+%%% Begin: To avoid problem with scrbook.cls (fncychap version 1.32)
+
+%%OUT:
+%\def\@schapter#1{\if@twocolumn
+% \@topnewpage[\@makeschapterhead{#1}]%
+% \else
+% \@makeschapterhead{#1}%
+% \@afterheading
+% \fi}
+
+%%IN:
+\def\@schapter#1{%
+\if@twocolumn%
+ \@makeschapterhead{#1}%
+\else%
+ \@makeschapterhead{#1}%
+ \@afterheading%
+\fi}
+
+%%% End: To avoid problem with scrbook.cls (fncychap version 1.32)
+
+\def\@makeschapterhead#1{%
+ \vspace*{50\p@}%
+ {\parindent \z@ \raggedright
+ \normalfont
+ \interlinepenalty\@M
+ \DOTIS{#1}
+ \vskip 40\p@
+ }}
+
+\endinput
+
+
diff --git a/larpe/tags/release-1.1.1/doc/en/larpe-admin.rst b/larpe/tags/release-1.1.1/doc/en/larpe-admin.rst
new file mode 100644
index 0000000..32ac1c9
--- /dev/null
+++ b/larpe/tags/release-1.1.1/doc/en/larpe-admin.rst
@@ -0,0 +1,202 @@
+=====================================
+Larpe - Administrator Guide
+=====================================
+
+:author: Damien Laniel
+:contact: dlaniel@entrouvert.com
+:copyright: Copyright © 2006 Entr'ouvert
+
+.. contents:: Table of contents
+
+Overview
+========
+
+Larpe is a Liberty Alliance Reverse Proxy. It allows any service provider
+(that is a website) to use Liberty Alliance features (Identity federation,
+Single Sign On and Single Logout) without changing the code of
+the service provider itself. It uses the Lasso_ library
+which is certified by the `Liberty Alliance`_ consortium. Lasso_ and Larpe
+are released under the terms of the `GNU GPL license`_.
+
+
+How to get and install Larpe
+============================
+
+Installation under Debian_ Sarge
+++++++++++++++++++++++++++++++++
+
+To work correctly Larpe relies on :
+
+* Apache2_ ;
+
+* Lasso_ (0.6.3) ;
+
+* Quixote_ (2.0) ;
+
+* SCGI_ ;
+
+* mod_python_ ;
+
+* libxml2 ;
+
+* mod_proxy_html.
+
+You will also need a Liberty Alliance Identity Provider, be it on the same server or not.
+We recommend Authentic_ for that need.
+
+Package Installation
+--------------------
+
+You need to add the following line to your /etc/apt/sources.list; this will
+give you access to the repository where Larpe is stored::
+
+ deb http://deb.entrouvert.org/ sarge main
+
+As root type::
+
+ apt-get update
+ apt-get install larpe
+
+And follow the debconf wizard to set it up.
+
+All the required packages are now installed and configured.
+
+You might need to change the "" in your apache2 configuration
+(/etc/apache2/sites-available/apache2-vhost-larpe) depending on how you
+previously configured apache.
+
+Don't forget to modify your /etc/hosts file if necessary. Larpe now works, the
+administration interface is reachable at http://your_domain_name/admin. The username
+and password are the ones you entered during the installation wizard.
+
+If you don't want to modify your sources.list file, you can manually dowload and
+install the required packages with the dpkg -i command :
+
+* Larpe, Authentic and Lasso on http://deb.entrouvert.org/ ;
+
+* Quixote 2.0 on http://authentic.labs.libre-entreprise.org/.
+
+Installation with another Linux distribution
+++++++++++++++++++++++++++++++++++++++++++++
+
+We suppose Apache2_, SCGI_, mod_python_, libxml2 and mod_proxy_html are already installed. You need then to
+download and install the following sources :
+
+* Lasso http://lasso.entrouvert.org ;
+
+* Quixote http://www.mems-exchange.org/software/Quixote/ ;
+
+* Authentic http://authentic.labs.libre-entreprise.org/ ;
+
+* Larpe http://labs.libre-entreprise.org/frs/?group_id=108.
+
+To install Larpe, uncompress the sources you have downloaded and launch the
+setup.py script ::
+
+ tar xzf larpe*.tar.gz
+ cd larpe*
+ python setup.py install
+
+You need then to configure Apache2_ correctly. You should use the provided apache2-vhost-larpe template and adapt to your configuration.
+
+Don't forget to modify your /etc/hosts file if necessary. Larpe now works, the
+administration interface is reachable at http://your_domain_name/admin.
+
+Basic Larpe configuration
+=========================
+
+Identity Provider configuration
++++++++++++++++++++++++++++++++
+
+If you don't have a configured Identity Provider yet, please read Authentic
+manual to set it up. Then you must have the metadata and public key of the Identity
+Provider to begin with Larpe.
+
+Then in Larpe administration interface, click on "Settings", then "Identity Provider".
+Fill in the metadata and public key that you've got from your Identity Provider then
+click Submit.
+Your Identity Provider is now configured in Larpe, you can then configure as many Service
+Providers as you want.
+
+Service Provider Configuration
+++++++++++++++++++++++++++++++
+
+Service Provider configuration
+------------------------------
+
+Click on "Hosts" then "New Host".
+
+Fill in the following parameters :
+
+* Label : the name you want to give to your Service Provider ;
+
+* Original Site Address : the root URL of your Service Provider ;
+
+* Authentication Page : if the page which contains the authentication form for
+ your Service Provider is on a separate page, fill the url of this page here ;
+
+* Authentication Form Page : if you didn't fill the previous field and if the
+ authentication form if not on the first page of your Service Provider either,
+ fill the url of the page which contains the authentication form here ;
+
+* Logout Address : when you want Single Sign On and Identity Federation, you probably
+ want Single Logout too. If so, fill the logout url of your original site here ;
+
+* Reversed Host Name : the domain name where you want to access your Service Provider
+ through the reverse proxy. It can be the domain name of Larpe or not ;
+
+Then click "Submit". Wait a few seconds then go to http://reversed_host_name/reverse_directory/
+to check if it works. If not, wait a bit more and try again. If it really doesn't work,
+please submit a bug report at http://labs.libre-entreprise.org/tracker/?func=add&group_id=108&atid=512
+
+Service Provider Example: Linuxfr
+---------------------------------
+
+To help you setup your own Service Provider, we provide an example of a working Service Provider
+to guide you.
+
+To setup Linuxfr, fill in the following parameters :
+
+* Label : Linuxfr ;
+
+* Original Site Address : http://linuxfr.org/ ;
+
+* Authentication Page : Nothing here ;
+
+* Authentication Form Page : http://linuxfr.org/pub/ ;
+
+* Logout Address : http://linuxfr.org/close_session.html ;
+
+* Reversed Host Name : linuxfr.reverse-proxy.example.com.
+
+With "reverse-proxy.example.com" being the hostname you've set up before for your reverse-proxy
+
+Don't forget to add this new hostname to your /etc/hosts as well.
+
+You can then go to the reversed Linuxfr at http://linuxfr.reverse-proxy.example.com/
+
+Service Provider Liberty Alliance final setup
+---------------------------------------------
+
+Now that you can access your Service Provider, you need a final step to use Liberty Alliance
+features. Click on "Hosts", the click on the "Edit" icon of the Service Provider you've
+just configured. Save the Service Provider Metadata (for ID-FF 1.2) and the Public Key
+(right click then "Save as"). Configure this Service Provider on your Identity Provider
+with these two files.
+
+Licenses
+========
+
+Larpe, Authentic_, Candle_ and Lasso_ are released under the terms of the
+`GNU GPL license`_.
+
+.. _Lasso: http://lasso.entrouvert.org/
+.. _`Liberty Alliance`: http://projectliberty.org/
+.. _`GNU GPL License`: http://www.gnu.org/copyleft/gpl.html
+.. _Debian: http://www.debian.org/
+.. _Apache2: http://httpd.apache.org/
+.. _Quixote: http://www.mems-exchange.org/software/Quixote
+.. _mod_python: http://www.modpython.org/
+.. _SCGI: http://www.mems-exchange.org/software/scgi/
+.. _Candle: http://candle.labs.libre-entreprise.org/
+.. _Authentic: http://www.entrouvert.com/fr/authentic/
diff --git a/larpe/tags/release-1.1.1/doc/scripts/removealpha.sh b/larpe/tags/release-1.1.1/doc/scripts/removealpha.sh
new file mode 100755
index 0000000..f29ee67
--- /dev/null
+++ b/larpe/tags/release-1.1.1/doc/scripts/removealpha.sh
@@ -0,0 +1,5 @@
+#! /bin/sh
+
+size=$(identify $1 | cut -d ' ' -f 3)
+composite $1 -size $(identify $1 | cut -d ' ' -f3) xc:white $2
+
diff --git a/larpe/tags/release-1.1.1/doc/scripts/rst2latex.py b/larpe/tags/release-1.1.1/doc/scripts/rst2latex.py
new file mode 100755
index 0000000..4036fbf
--- /dev/null
+++ b/larpe/tags/release-1.1.1/doc/scripts/rst2latex.py
@@ -0,0 +1,29 @@
+#! /usr/bin/python
+
+"""A minimal reST frontend, to create appropriate LaTeX files."""
+
+try:
+ import locale
+ locale.setlocale(locale.LC_ALL, '')
+except:
+ pass
+
+from docutils.core import publish_cmdline, Publisher
+
+def set_io(self, source_path=None, destination_path=None):
+ Publisher.set_io_orig(self, source_path, destination_path='/dev/null')
+
+Publisher.set_io_orig, Publisher.set_io = Publisher.set_io, set_io
+
+output = publish_cmdline(writer_name='latex',
+ settings_overrides = {
+ 'documentclass': 'report',
+ 'documentoptions': '11pt,a4paper,titlepage',
+ 'use_latex_toc': True,
+ 'use_latex_docinfo': True,
+ 'stylesheet': 'custom.tex'})
+
+output = output.replace('\\includegraphics',
+ '\\includegraphics[width=.9\\textwidth,height=15cm,clip,keepaspectratio]')
+output = output.replace('\\begin{figure}[htbp]', '\\begin{figure}[H]')
+print output
diff --git a/larpe/tags/release-1.1.1/exclude_from_dist b/larpe/tags/release-1.1.1/exclude_from_dist
new file mode 100644
index 0000000..d6e841d
--- /dev/null
+++ b/larpe/tags/release-1.1.1/exclude_from_dist
@@ -0,0 +1,12 @@
+.svn
+*.pyc
+*.pyo
+*.pye
+*.ptle
+*.swp
+debian.sarge
+make_debian_package.sh
+build
+dist
+tests
+larpe/filter
diff --git a/larpe/tags/release-1.1.1/fedora/larpe-reload-apache2-script b/larpe/tags/release-1.1.1/fedora/larpe-reload-apache2-script
new file mode 100755
index 0000000..928b0b7
--- /dev/null
+++ b/larpe/tags/release-1.1.1/fedora/larpe-reload-apache2-script
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# The command "/etc/init.d/httpd reload" on Fedora actually _restarts_ Apache
+# We need to _reload_ it without closing existing connections
+
+APACHE2CTL=/usr/sbin/apachectl
+
+echo -n "Testing Apache config... "
+if ! $APACHE2CTL configtest > /dev/null 2>&1; then
+ $APACHE2CTL configtest || true
+ echo "[FAILED]"
+ exit 1
+else
+ echo "[OK]"
+fi
+echo -n "Reloading Apache config... "
+if $APACHE2CTL graceful $2 ; then
+ echo "[OK]"
+else
+ echo "[FAILED]"
+fi
+
diff --git a/larpe/tags/release-1.1.1/fedora/larpe.init b/larpe/tags/release-1.1.1/fedora/larpe.init
new file mode 100755
index 0000000..a41681f
--- /dev/null
+++ b/larpe/tags/release-1.1.1/fedora/larpe.init
@@ -0,0 +1,104 @@
+#! /bin/bash
+#
+# larpe Startup script for the Larpe reverse proxy
+#
+# description: Larpe is Liberty Alliance reverse proxy. It is used to add Liberty Alliance \
+# features to some sites without modifying the sites themselves.
+# processname: larpe
+# config: /etc/httpd/conf.d/larpe.conf
+# config: /etc/larpe/apache2-vhost-larpe-common
+# config: /etc/sysconfig/larpe
+# pidfile: /var/run/larpe.pid
+#
+
+### BEGIN INIT INFO
+# Provides: larpe
+# Required-Start: $local_fs $network
+# Required-Stop: $local_fs $network
+# Default-Start:
+# Default-Stop: 0 1 2 3 4 5 6
+# Short-Description: Start Larpe Liberty Alliance reverse proxy
+# Description: Start Larpe Liberty Alliance reverse proxy
+### END INIT INFO
+
+prog=larpe
+LARPECTL=/usr/sbin/larpectl
+PIDFILE=/var/run/larpe.pid
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+# Check that networking is up.
+[ ${NETWORKING} = "no" ] && exit 0
+
+[ -x $LARPECTL ] || exit 5
+
+# Read config file if it is present.
+if [ -f /etc/sysconfig/larpe ]; then
+ . /etc/sysconfig/larpe
+fi
+
+RETVAL=0
+
+#
+# Function that starts the daemon/service.
+#
+start() {
+ echo -n $"Starting $prog: "
+ $LARPECTL start &
+ RETVAL=$?
+ if [ $RETVAL -eq 0 ]
+ then
+ touch /var/lock/subsys/$prog
+ fi
+ echo
+ return $RETVAL
+}
+
+#
+# Function that stops the daemon/service.
+#
+stop() {
+ echo -n $"Stopping $prog: "
+ killproc $LARPECTL
+ RETVAL=$?
+ if [ $RETVAL -eq 0 ]
+ then
+ rm -rf /var/lock/subsys/$prog
+ fi
+ echo
+ return $RETVAL
+}
+
+# See how we were called.
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ status)
+ status $prog
+ RETVAL=$?
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ condrestart)
+ if [ -f ${PIDFILE} ] ; then
+ stop
+ start
+ fi
+ ;;
+ *)
+ echo $"Usage: $prog {start|stop|restart|condrestart|status}"
+ exit 1
+esac
+
+exit $RETVAL
+
diff --git a/larpe/tags/release-1.1.1/fedora/larpe.spec b/larpe/tags/release-1.1.1/fedora/larpe.spec
new file mode 100644
index 0000000..21932dc
--- /dev/null
+++ b/larpe/tags/release-1.1.1/fedora/larpe.spec
@@ -0,0 +1,139 @@
+%{!?python_sitearch: %define python_sitearch %(%{__python} -c 'from distutils import sysconfig; print sysconfig.get_python_lib(1)')}
+# eval to 2.3 if python isn't yet present, workaround for no python in fc4 minimal buildroot
+%{!?python_version: %define python_version %(%{__python} -c 'import sys; print sys.version.split(" ")[0]' || echo "2.3")}
+%define apacheconfdir %{_sysconfdir}/httpd/conf.d
+
+Summary: Liberty Alliance Reverse Proxy
+Name: larpe
+Version: 0.2.9
+Release: 2%{?dist}
+License: GPL
+Group: System Environment/Applications
+Url: http://larpe.labs.libre-entreprise.org/
+Source0: http://labs.libre-entreprise.org/frs/download.php/591/%{name}-%{version}.tar.gz
+Buildroot: %{_tmppath}/%{name}-%{version}-%(id -u -n)
+BuildRequires: python >= 2.3, python-quixote >= 2.0
+BuildRequires: gettext
+Requires: httpd >= 2.0, mod_scgi, mod_proxy_html
+Requires: lasso-python >= 0.6.3, python-quixote >= 2.0, python-scgi
+Requires: initscripts
+Requires(post): /sbin/chkconfig
+Requires(preun):/sbin/chkconfig
+Requires(preun): /sbin/service
+
+%description
+Larpe is a Liberty Alliance Reverse Proxy. It allows any service provider (that is a website)
+to use Liberty Alliance features (Identity federation, Single Sign On and Single Logout) without
+changing the code of the service provider itself.
+
+It uses the Lasso library which is certified by the Liberty Alliance consortium.
+
+It is a quixote application and is commonly runned inside Apache web server.
+
+%package doc
+Summary: Documentation files for %{name} development.
+Group: Documentation
+BuildRequires: python-docutils, tetex-latex
+
+%description doc
+This package contains development documentation for %{name}.
+
+%prep
+%setup -q
+
+# Change Apache vhost path in Larpe config
+sed -i s#"/var/log/apache2/larpe-access.log"#"logs/larpe_access_log combined\n TransferLog logs/larpe_access_log"# conf/apache2-vhost-larpe
+sed -i s#"/var/log/apache2/larpe-error.log"#"logs/larpe_error_log"# conf/apache2-vhost-larpe
+sed -i s#"APACHE_MAIN_VHOST.*$"#"APACHE_MAIN_VHOST='/etc/httpd/conf.d/larpe.conf'"# larpe/Defaults.py
+
+%build
+
+%install
+rm -rf %{buildroot}
+
+# install generic files
+make install prefix=%{_prefix} DESTDIR=%{buildroot}
+
+# install init script
+install -d %{buildroot}/%{_initrddir}
+install -p -m 0755 fedora/larpe.init %{buildroot}%{_initrddir}/larpe
+
+# apache configuration
+mkdir -p %{buildroot}%{apacheconfdir}
+install -p -m 644 conf/apache2-vhost-larpe %{buildroot}%{apacheconfdir}/larpe.conf
+
+# apache reload script
+install -p -m 0755 fedora/larpe-reload-apache2-script %{buildroot}%{_sbindir}/
+
+# install doc files
+install -d -m 0755 %{buildroot}%{_datadir}/gtk-doc/html/larpe
+make -C doc DESTDIR=%{buildroot}%{_datadir}/gtk-doc/html/larpe
+
+%clean
+rm -fr %{buildroot}
+
+%post
+/sbin/chkconfig --add %{name}
+
+# manual post-installation
+cat <<_EOF_
+You must edit first %{apacheconfdir}/larpe.conf
+
+You must enable Larpe with "chkconfig larpe on ; service larpe start"
+
+You must also restart Apache with "service httpd restart"!
+_EOF_
+
+%preun
+if [ $1 = 0 ]; then
+ /sbin/service %{name} stop > /dev/null 2>&1
+ /sbin/chkconfig --del %{name}
+fi
+
+%files
+%defattr(-,root,root,755)
+%config %{_initrddir}/larpe
+%config(noreplace) %{apacheconfdir}/larpe.conf
+%config(noreplace) %{_sysconfdir}/larpe/apache2-vhost-larpe-common
+%{_sbindir}/larpectl
+%{_sbindir}/larpe-reload-apache2
+%{_sbindir}/larpe-reload-apache2-script
+%{python_sitearch}/%{name}
+%{_datadir}/%{name}
+%{_datadir}/locale/fr/LC_MESSAGES/larpe.mo
+/var/lib/larpe
+%defattr(644,root,root,755)
+%doc AUTHORS COPYING NEWS README
+
+%files doc
+%defattr(-,root,root)
+%doc %{_datadir}/gtk-doc/html/%{name}
+
+%changelog
+* Tue Mar 05 2009 Jean-Marc Liger 0.2.9-2
+- Added missing BuildRequires gettext
+- Enabled larpe init script
+
+* Mon Jan 19 2009 Damien Laniel 0.2.9-1
+- Updated to 0.2.9
+- Use Larpe Makefile to install generic files
+- Copy fedora specific files
+
+* Sat Jan 17 2009 Jean-Marc Liger 0.2.1-2
+- Added missing BuildRequires tetex-latex for doc subpackage
+- Rebuilt on CentOS 4,5
+
+* Wed Jan 14 2009 Jean-Marc Liger 0.2.1-1
+- Updated to 0.2.1
+- Added missing Requires lasso-python
+- Added missing Requires python-scgi
+- Rebuilt on CentOS 4,5
+
+* Fri Mar 02 2007 Jean-Marc Liger 0.2.0-1
+- Updated to 0.2.0
+- Added BuildRequires python-quixote
+- Built on Fedora Core 3 / RHEL 4 and Fedora Core 6 / RHEL 5
+
+* Wed Jan 24 2007 Jean-Marc Liger 0.1.0-1
+- First 0.1.0
+- Built on Fedora Core 3 / RHEL 4 and Fedora Core 6 / RHEL 5
diff --git a/larpe/tags/release-1.1.1/larpe-reload-apache2.c b/larpe/tags/release-1.1.1/larpe-reload-apache2.c
new file mode 100644
index 0000000..73f681a
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe-reload-apache2.c
@@ -0,0 +1,173 @@
+/*
+ Template for a setuid program that calls a script.
+
+ The script should be in an unwritable directory and should itself
+ be unwritable. In fact all parent directories up to the root
+ should be unwritable. The script must not be setuid, that's what
+ this program is for.
+
+ This is a template program. You need to fill in the name of the
+ script that must be executed. This is done by changing the
+ definition of FULL_PATH below.
+
+ There are also some rules that should be adhered to when writing
+ the script itself.
+
+ The first and most important rule is to never, ever trust that the
+ user of the program will behave properly. Program defensively.
+ Check your arguments for reasonableness. If the user is allowed to
+ create files, check the names of the files. If the program depends
+ on argv[0] for the action it should perform, check it.
+
+ Assuming the script is a Bourne shell script, the first line of the
+ script should be
+ #!/bin/sh -
+ The - is important, don't omit it. If you're using esh, the first
+ line should be
+ #!/usr/local/bin/esh -f
+ and for ksh, the first line should be
+ #!/usr/local/bin/ksh -p
+ The script should then set the variable IFS to the string
+ consisting of , , and . After this (*not*
+ before!), the PATH variable should be set to a reasonable value and
+ exported. Do not expect the PATH to have a reasonable value, so do
+ not trust the old value of PATH. You should then set the umask of
+ the program by calling
+ umask 077 # or 022 if you want the files to be readable
+ If you plan to change directories, you should either unset CDPATH
+ or set it to a good value. Setting CDPATH to just ``.'' (dot) is a
+ good idea.
+ If, for some reason, you want to use csh, the first line should be
+ #!/bin/csh -fb
+ You should then set the path variable to something reasonable,
+ without trusting the inherited path. Here too, you should set the
+ umask using the command
+ umask 077 # or 022 if you want the files to be readable
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* CONFIGURATION SECTION */
+
+#ifndef FULL_PATH/* so that this can be specified from the Makefile */
+ #define FULL_PATH "/usr/sbin/larpe-reload-apache2-script"
+#endif
+#ifndef UMASK
+ #define UMASK 077
+#endif
+
+/* END OF CONFIGURATION SECTION */
+
+#if defined(__STDC__) && defined(__sgi)
+#define environ _environ
+#endif
+
+/* don't change def_IFS */
+char def_IFS[] = "IFS= \t\n";
+/* you may want to change def_PATH, but you should really change it in */
+/* your script */
+#ifdef __sgi
+char def_PATH[] = "PATH=/usr/bsd:/usr/bin:/bin:/usr/local/bin:/usr/sbin";
+#else
+char def_PATH[] = "PATH=/usr/ucb:/usr/bin:/bin:/usr/local/bin";
+#endif
+/* don't change def_CDPATH */
+char def_CDPATH[] = "CDPATH=.";
+/* don't change def_ENV */
+char def_ENV[] = "ENV=:";
+
+/*
+ This function changes all environment variables that start with LD_
+ into variables that start with XD_. This is important since we
+ don't want the script that is executed to use any funny shared
+ libraries.
+
+ The other changes to the environment are, strictly speaking, not
+ needed here. They can safely be done in the script. They are done
+ here because we don't trust the script writer (just like the script
+ writer shouldn't trust the user of the script).
+ If IFS is set in the environment, set it to space,tab,newline.
+ If CDPATH is set in the environment, set it to ``.''.
+ Set PATH to a reasonable default.
+*/
+void
+clean_environ(void)
+{
+ char **p;
+ extern char **environ;
+
+ for (p = environ; *p; p++) {
+ if (strncmp(*p, "LD_", 3) == 0)
+ **p = 'X';
+ else if (strncmp(*p, "_RLD", 4) == 0)
+ **p = 'X';
+ else if (strncmp(*p, "PYTHON", 6) == 0)
+ **p = 'X';
+ else if (strncmp(*p, "IFS=", 4) == 0)
+ *p = def_IFS;
+ else if (strncmp(*p, "CDPATH=", 7) == 0)
+ *p = def_CDPATH;
+ else if (strncmp(*p, "ENV=", 4) == 0)
+ *p = def_ENV;
+ }
+ putenv(def_PATH);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct stat statb;
+ gid_t egid = getegid();
+ uid_t euid = geteuid();
+
+ /*
+ Sanity check #1.
+ This check should be made compile-time, but that's not possible.
+ If you're sure that you specified a full path name for FULL_PATH,
+ you can omit this check.
+ */
+ if (FULL_PATH[0] != '/') {
+ fprintf(stderr, "%s: %s is not a full path name\n", argv[0],
+ FULL_PATH);
+ fprintf(stderr, "You can only use this wrapper if you\n");
+ fprintf(stderr, "compile it with an absolute path.\n");
+ exit(1);
+ }
+
+ /*
+ Sanity check #2.
+ Check that the owner of the script is equal to either the
+ effective uid or the super user.
+ */
+ if (stat(FULL_PATH, &statb) < 0) {
+ perror("stat");
+ exit(1);
+ }
+ if (statb.st_uid != 0 && statb.st_uid != euid) {
+ fprintf(stderr, "%s: %s has the wrong owner\n", argv[0],
+ FULL_PATH);
+ fprintf(stderr, "The script should be owned by root,\n");
+ fprintf(stderr, "and shouldn't be writeable by anyone.\n");
+ exit(1);
+ }
+
+ if (setregid(egid, egid) < 0)
+ perror("setregid");
+ if (setreuid(euid, euid) < 0)
+ perror("setreuid");
+
+ clean_environ();
+
+ umask(UMASK);
+
+ while (**argv == '-')/* don't let argv[0] start with '-' */
+ (*argv)++;
+ execv(FULL_PATH, argv);
+ fprintf(stderr, "%s: could not execute the script\n", argv[0]);
+ exit(1);
+}
diff --git a/larpe/tags/release-1.1.1/larpe/Defaults.py b/larpe/tags/release-1.1.1/larpe/Defaults.py
new file mode 100644
index 0000000..ca723a0
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/Defaults.py
@@ -0,0 +1,9 @@
+'''Default global variables'''
+
+APP_DIR = '/var/lib/larpe'
+DATA_DIR = '/usr/share/larpe'
+ERROR_LOG = None #'/var/log/larpe.log'
+WEB_ROOT = 'larpe'
+APACHE_MAIN_VHOST = '/etc/apache2/sites-available/apache2-vhost-larpe'
+APACHE_VHOST_COMMON = '/etc/larpe/apache2-vhost-larpe-common'
+APACHE_RELOAD = '/usr/sbin/larpe-reload-apache2'
diff --git a/larpe/tags/release-1.1.1/larpe/__init__.py b/larpe/tags/release-1.1.1/larpe/__init__.py
new file mode 100644
index 0000000..ee83c50
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/__init__.py
@@ -0,0 +1,21 @@
+'''Setup path and load Lasso library'''
+try:
+ from quixote.ptl import compile_package
+ compile_package(__path__)
+except ImportError:
+ pass
+
+import sys
+import os
+sys.path.insert(0, os.path.dirname(__file__))
+
+import qommon
+
+try:
+ import lasso
+except ImportError:
+ lasso = None
+
+if lasso and not hasattr(lasso, 'SAML2_SUPPORT'):
+ lasso.SAML2_SUPPORT = False
+
diff --git a/larpe/tags/release-1.1.1/larpe/admin/__init__.py b/larpe/tags/release-1.1.1/larpe/admin/__init__.py
new file mode 100644
index 0000000..d6622e6
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/__init__.py
@@ -0,0 +1,6 @@
+try:
+ from quixote.ptl import compile_package
+ compile_package(__path__)
+except ImportError:
+ pass
+from root import RootDirectory
diff --git a/larpe/tags/release-1.1.1/larpe/admin/apache.py b/larpe/tags/release-1.1.1/larpe/admin/apache.py
new file mode 100644
index 0000000..cb01760
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/apache.py
@@ -0,0 +1,300 @@
+import os
+import re
+import urllib
+import base64
+
+from quixote import get_publisher, get_request
+
+from qommon import get_cfg
+
+from larpe.hosts import Host
+from larpe.Defaults import APP_DIR, APACHE_MAIN_VHOST, APACHE_VHOST_COMMON, APACHE_RELOAD
+
+def write_apache2_vhosts():
+ hosts = Host.select(lambda x: x.name != 'larpe')
+ hosts.sort()
+ vhosts_dir = os.path.join(APP_DIR, 'vhosts.d')
+ vhost_locations_dir = os.path.join(APP_DIR, 'vhost-locations.d')
+ vhosts_dir_disabled = os.path.join(APP_DIR, 'vhosts.d.disabled')
+ vhost_locations_dir_disabled = os.path.join(APP_DIR, 'vhost-locations.d.disabled')
+ vhost_file_name = get_request().get_server().split(':')[0]
+ vhost_file = None
+ reversed_hostname = ''
+ vhost = None
+
+ if get_publisher().cfg.get(str('allow_config_generation'), True):
+ vhost_file = open(os.path.join(vhosts_dir, vhost_file_name), 'w')
+ locations_file = open(os.path.join(vhost_locations_dir, vhost_file_name), 'w')
+ else:
+ vhost_file = open(os.path.join(vhosts_dir_disabled, vhost_file_name), 'w')
+ locations_file = open(os.path.join(vhost_locations_dir_disabled, vhost_file_name), 'w')
+ try:
+ main_vhost = open(APACHE_MAIN_VHOST, 'r')
+ file_content = main_vhost.read()
+ regexp = re.compile('')
+ vhost_ip = regexp.findall(file_content)[0]
+ except (IOError, IndexError):
+ vhost_ip = '*'
+
+ for host in hosts:
+ if host.orig_site is None:
+ # This site hasn't been fully configured
+ continue
+ if host.reversed_hostname != reversed_hostname \
+ and host.reversed_hostname != get_cfg('proxy_hostname'):
+ if vhost is not None:
+ vhost.close()
+ vhost = Vhost(host, vhost_ip)
+ vhost.write(vhost_file)
+ reversed_hostname = host.reversed_hostname
+
+ if host.reversed_hostname == get_cfg('proxy_hostname'):
+ conf_file = locations_file
+ else:
+ conf_file = vhost_file
+
+ Location(host).write(conf_file)
+
+ if vhost_file is not None:
+ if vhost is not None:
+ vhost.close()
+ vhost_file.close()
+ if locations_file is not None:
+ locations_file.close()
+
+ if get_publisher().cfg.get(str('allow_config_generation'), True):
+ os.system(APACHE_RELOAD)
+
+
+class Vhost(object):
+ def __init__(self, host, main_ip_port):
+ self.host = host
+ self.main_ip_port = main_ip_port
+ self.conf_file = None
+
+ def get_ip_port(self):
+ if self.host.scheme == 'https':
+ return self.main_ip_port.replace(':80', ':443')
+ else:
+ return self.main_ip_port.replace(':443', ':80')
+ ip_port = property(get_ip_port)
+
+ def get_proxy_url(self):
+ if get_cfg('proxy', {}).get('enabled') and self.host.use_proxy == True:
+ return 'http://%(ip)s:%(port)s' % get_cfg('proxy', {})
+ return None
+ proxy_url = property(get_proxy_url)
+
+ def get_proxy_auth(self):
+ if self.get_proxy_url() and get_cfg('proxy', {}).get('user'):
+ credentials = base64.encodestring(
+ '%(user)s:%(password)s' % get_cfg('proxy', {}))[:-1]
+ return '"Basic %s"' % credentials
+ return None
+ proxy_auth = property(get_proxy_auth)
+
+ def get_cfg(self):
+ return { 'ip_port': self.ip_port,
+ 'reversed_hostname': self.host.reversed_hostname,
+ 'proxy_url': self.proxy_url,
+ 'proxy_auth': self.proxy_auth }
+ cfg = property(get_cfg)
+
+ def write(self, conf_file):
+ self.conf_file = conf_file
+ conf_lines = []
+ # Start Virtual Host
+ conf_lines.append('' % self.cfg)
+ # Server name and administrator
+ conf_lines.append('ServerName %(reversed_hostname)s' % self.cfg)
+ conf_lines.append('# ServerAdmin root@localhost\n')
+ # Include common vhost configuration
+ conf_lines.append('include %s\n' % APACHE_VHOST_COMMON)
+ # SSL
+ if self.host.scheme == 'https':
+ conf_lines.append('SSLEngine On\n')
+ if self.host.orig_site.startswith('https'):
+ conf_lines.append('SSLProxyEngine On\n')
+ # Remote proxy configuration
+ if self.proxy_url is not None:
+ conf_lines.append('ProxyRemote * %(proxy_url)s' % self.cfg)
+ if self.proxy_auth is not None:
+ conf_lines.append(
+ 'RequestHeader set Proxy-Authorization %(proxy_auth)s\n' % self.cfg)
+ # Write it all
+ conf_file.write('\n\t'.join(conf_lines))
+
+ def close(self):
+ if self.conf_file:
+ self.conf_file.write(' \n\n')
+
+
+def apache_escape_chars(url):
+ special_characters = ('\\', '.', '?', '*', '+', '^', '$', '|', '(', ')', '[', ']')
+ for char in special_characters:
+ url = url.replace(char, '\%s' % char)
+ return url
+
+class Location(object):
+ def __init__(self, host):
+ self.host = host
+
+ def get_reversed_directory(self):
+ if not self.host.reversed_directory:
+ return '%s/' % get_request().environ['SCRIPT_NAME']
+ else:
+ return '%s/%s/' % (get_request().environ['SCRIPT_NAME'], self.host.reversed_directory)
+ reversed_directory = property(get_reversed_directory)
+
+ def get_python_path(self):
+ if self.host.apache_output_python_filters and \
+ self.host.apache_python_paths:
+ python_path = 'PythonPath "sys.path'
+ for path in self.host.apache_python_paths:
+ python_path += "+['%s']" % path
+ python_path += '"'
+ return python_path
+ else:
+ return None
+ python_path = property(get_python_path)
+
+ def get_output_filters(self):
+ python_filters = ''
+ output_filters = []
+ if self.host.apache_output_python_filters:
+ i = 0
+ for filter_file in self.host.apache_output_python_filters:
+ filter_name = 'filter%d' % i
+ python_filters += 'PythonOutputFilter %s %s\n\t\t' % (filter_file, filter_name)
+ output_filters.append(filter_name)
+ i += 1
+ if self.host.apache_output_filters:
+ for output_filter in self.host.apache_output_filters:
+ output_filters.append(output_filter)
+ if output_filters:
+ return python_filters + 'SetOutputFilter ' + ';'.join(output_filters)
+ else:
+ return None
+ output_filters = property(get_output_filters)
+
+ def get_old_auth_url(self):
+ old_auth_url = None
+ if self.host.initiate_sso_url:
+ old_auth_url = self.host.initiate_sso_url
+ elif self.host.auth_url is not None:
+ if self.host.auth_url.startswith('http://'):
+ chars_to_skip = 5
+ else:
+ chars_to_skip = 6
+ regexp = re.compile(self.host.orig_site[chars_to_skip:])
+ old_auth_url_short = regexp.sub('', self.host.auth_url[chars_to_skip:])
+ if old_auth_url_short.startswith('/'):
+ old_auth_url = old_auth_url_short
+ else:
+ old_auth_url = '/' + old_auth_url_short
+ if old_auth_url:
+ old_auth_url = apache_escape_chars(old_auth_url)
+ return old_auth_url
+ old_auth_url = property(get_old_auth_url)
+
+ def get_new_auth_url(self):
+ if not hasattr(self.host, 'base_url'):
+ return None
+ base_url_tokens = self.host.base_url.split('/')
+ base_url_tokens[-1] = 'login'
+ return '/'.join(base_url_tokens)
+ new_auth_url = property(get_new_auth_url)
+
+ def get_old_logout_url(self):
+ old_logout_url = None
+ if self.host.logout_url is not None:
+ if self.host.logout_url.startswith('http://'):
+ chars_to_skip = 5
+ else:
+ chars_to_skip = 6
+ regexp = re.compile(self.host.orig_site[chars_to_skip:])
+ old_logout_url_short = regexp.sub('', self.host.logout_url[chars_to_skip:])
+ if old_logout_url_short.startswith('/'):
+ old_logout_url = old_logout_url_short
+ else:
+ old_logout_url = '/' + old_logout_url_short
+ old_logout_url = apache_escape_chars(old_logout_url)
+ return old_logout_url
+ old_logout_url = property(get_old_logout_url)
+
+ def get_new_logout_url(self):
+ if not hasattr(self.host, 'base_url'):
+ return None
+ base_url_tokens = self.host.base_url.split('/')
+ base_url_tokens[-1] = 'logout'
+ return '/'.join(base_url_tokens)
+ new_logout_url = property(get_new_logout_url)
+
+ def get_orig_site_url_and_dir(self):
+ # Split url
+ if self.host.orig_site.startswith('http://'):
+ orig_host, orig_query = urllib.splithost(self.host.orig_site[5:])
+ else:
+ orig_host, orig_query = urllib.splithost(self.host.orig_site[6:])
+ # Add a trailing slash if necessary
+ if self.host.orig_site.endswith('/'):
+ orig_url = self.host.orig_site
+ orig_dir = orig_query
+ else:
+ orig_url = self.host.orig_site + '/'
+ orig_dir = orig_query + '/'
+ return orig_url, orig_dir
+
+ def get_orig_url(self):
+ orig_url, orig_dir = self.get_orig_site_url_and_dir()
+ return orig_url
+ orig_url = property(get_orig_url)
+
+ def get_orig_dir(self):
+ orig_url, orig_dir = self.get_orig_site_url_and_dir()
+ return orig_dir
+ orig_dir = property(get_orig_dir)
+
+ def get_cfg(self):
+ return { 'reversed_directory': self.reversed_directory,
+ 'old_auth_url': self.old_auth_url,
+ 'new_auth_url': self.new_auth_url,
+ 'old_logout_url': self.old_logout_url,
+ 'new_logout_url': self.new_logout_url,
+ 'orig_url': self.orig_url,
+ 'orig_dir': self.orig_dir }
+ cfg = property(get_cfg)
+
+ def write(self, conf_file):
+ conf_lines = []
+ # Start Location
+ conf_lines.append('\n\t' % self.cfg)
+ # No user restriction
+ conf_lines.append('Allow from all')
+ # Apache output filters
+ if self.python_path:
+ conf_lines.append(self.python_path)
+ if self.output_filters:
+ conf_lines.append(self.output_filters)
+ # Redirect rules
+ # Redirect old authentication url to the new one
+ if self.old_auth_url is not None and self.host.auth_form_places == 'form_once':
+ conf_lines.append(
+ 'RedirectMatch %(old_auth_url)s %(new_auth_url)s' % self.cfg)
+ # Redirect old logout url to the new one
+ if self.old_logout_url is not None:
+ conf_lines.append(
+ 'RedirectMatch %(old_logout_url)s %(new_logout_url)s' % self.cfg)
+ # Redirect the home page to the login page
+ if self.host.redirect_root_to_login is True:
+ conf_lines.append('RedirectMatch ^/$ %(new_auth_url)s' % self.cfg)
+ # Convert urls in http headers to/from the new domain
+ conf_lines.append('ProxyPass %(orig_url)s' % self.cfg)
+ conf_lines.append('ProxyPassReverse %(orig_url)s' % self.cfg)
+ # Convert urls in html pages to/from the new domain
+ conf_lines.append('ProxyHTMLURLMap %(orig_dir)s %(reversed_directory)s' % self.cfg)
+ conf_lines.append('ProxyHTMLURLMap %(orig_url)s %(reversed_directory)s' % self.cfg)
+ # Write it all and close the Location
+ conf_file.write('\n\t\t'.join(conf_lines))
+ conf_file.write('\n\t \n')
+
diff --git a/larpe/tags/release-1.1.1/larpe/admin/fields_prefill.ptl b/larpe/tags/release-1.1.1/larpe/admin/fields_prefill.ptl
new file mode 100644
index 0000000..c927388
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/fields_prefill.ptl
@@ -0,0 +1,130 @@
+from quixote import get_response, redirect
+from quixote.directory import Directory
+
+from qommon.form import *
+from qommon.admin.menu import html_top, command_icon
+
+from field_prefill import FieldPrefill
+
+class FieldUI:
+ def __init__(self, field_prefill):
+ self.field_prefill = field_prefill
+
+ def form_edit(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(StringWidget, 'name', title = _('Field name'), required = True,
+ size = 50, value = self.field_prefill.name)
+ form.add(StringWidget, 'xpath', title = _('Xpath of the attribute'), required = True,
+ size = 50, value = self.field_prefill.xpath, hint=_('Example: /pp:PP/pp:InformalName'))
+ form.add(IntWidget, 'number', title = _('Number of the field in the data'), required = True,
+ size = 3, value = self.field_prefill.number,
+ hint=_('Change it if there are multiple fields corresponding to the same Xpath and you want to get another than the first one'))
+ form.add(CheckboxWidget, 'raw_xml', title=_('Get raw XML value'),
+ value = self.field_prefill.raw_xml)
+ form.add(StringWidget, 'regexp_match', title = _('Python regexp of a string to match'),
+ size = 50, value = self.field_prefill.regexp_match)
+ form.add(StringWidget, 'regexp_replacing', title = _('Python regexp of the replacing string'),
+ size = 50, value = self.field_prefill.regexp_replacing)
+ form.add(WidgetDict, 'select_options', title = _('Options mapping for a select field'),
+ value = self.field_prefill.select_options, add_element_label = _('Add item'))
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ return form
+
+ def submit_edit(self, form):
+ for f in ('name', 'xpath', 'number', 'raw_xml', 'regexp_match', 'regexp_replacing', 'select_options'):
+ widget = form.get_widget(f)
+ setattr(self.field_prefill, f, widget.parse())
+ self.field_prefill.store()
+
+class FieldPage(Directory):
+ _q_exports = ['', 'delete']
+
+ def __init__(self, field_id):
+ self.field_prefill = FieldPrefill.get(field_id)
+ self.field_ui = FieldUI(self.field_prefill)
+ get_response().breadcrumb.append((field_id + '/', field_id))
+
+ def _q_index [html] (self):
+ form = self.field_ui.form_edit()
+ redo = False
+
+ if form.get_widget('cancel').parse():
+ return redirect('..')
+
+ if form.get_widget('select_options') and form.get_widget('select_options').get_widget('add_element').parse():
+ form.clear_errors()
+ redo = True
+
+ if redo is False and form.is_submitted() and not form.has_errors():
+ self.field_ui.submit_edit(form)
+ return redirect('..')
+
+ get_response().breadcrumb.append( ('edit', _('Edit')) )
+ html_top('edit', title = _('Edit'))
+ '%s ' % _('Edit')
+
+ form.render()
+
+ def delete [html] (self):
+ form = Form(enctype='multipart/form-data')
+ form.widgets.append(HtmlWidget('%s
' % _(
+ 'You are about to irrevocably delete this field.')))
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ if form.get_widget('cancel').parse():
+ return redirect('..')
+ if not form.is_submitted() or form.has_errors():
+ get_response().breadcrumb.append(('delete', _('Delete')))
+ html_top('delete_form', title = _('Delete Field'))
+ '%s : %s ' % (_('Delete Field'), self.field_prefill.id)
+ form.render()
+ else:
+ self.field_prefill.remove_self()
+ return redirect('..')
+
+
+class FieldsDirectory(Directory):
+ _q_exports = ['', 'new']
+
+ def __init__(self, form_prefill):
+ get_response().breadcrumb.append(('fields/', _('Fields')))
+ self.form_prefill = form_prefill
+
+ def _q_lookup(self, component):
+ return FieldPage(component)
+
+ def _q_index [html] (self):
+ html_top('fields', title = _('Fields'))
+ """""" % _('New Field')
+
+ ''
+
+ def new [html] (self):
+ get_response().breadcrumb.append(('new', _('New')) )
+ field_prefill = FieldPrefill()
+ field_prefill.form_id = self.form_prefill.id
+ field_prefill.store()
+ return redirect('%s/' % field_prefill.id)
+
diff --git a/larpe/tags/release-1.1.1/larpe/admin/forms_prefill.ptl b/larpe/tags/release-1.1.1/larpe/admin/forms_prefill.ptl
new file mode 100644
index 0000000..0f34eff
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/forms_prefill.ptl
@@ -0,0 +1,127 @@
+from quixote import get_response, redirect
+from quixote.directory import Directory
+
+from qommon.form import *
+from qommon.admin.menu import html_top, command_icon
+
+from form_prefill import FormPrefill
+from fields_prefill import FieldsDirectory
+
+class FormUI:
+ def __init__(self, form_prefill):
+ self.form_prefill = form_prefill
+
+ def form_edit(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(StringWidget, 'name', title = _('Form name'), required = True,
+ size = 50, value = self.form_prefill.name, hint=_('Only used for display'))
+ form.add(UrlWidget, 'url', title = _('Form address'), required = True,
+ size = 50, value = self.form_prefill.url)
+ form.add(StringWidget, 'profile', title = _('ID-WSF data profile'), required = True,
+ size = 50, value = self.form_prefill.profile, hint=_('Example: urn:liberty:id-sis-pp:2005-05'))
+ form.add(StringWidget, 'prefix', title = _('ID-WSF data XML prefix'), required = True,
+ size = 50, value = self.form_prefill.prefix, hint=_('Example: pp'))
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ return form
+
+ def submit_edit(self, form):
+ for f in ('name', 'url', 'profile', 'prefix'):
+ widget = form.get_widget(f)
+ setattr(self.form_prefill, f, widget.parse())
+ self.form_prefill.store()
+
+class FormPage(Directory):
+ _q_exports = ['', 'edit', 'delete']
+
+ def __init__(self, form_id):
+ self.form_prefill = FormPrefill.get(form_id)
+ self.form_ui = FormUI(self.form_prefill)
+ get_response().breadcrumb.append((form_id + '/', form_id))
+
+ def _q_index [html] (self):
+ html_top('forms_prefill', title = 'Form prefilling')
+
+ '%s ' % _('Form prefilling configuration')
+ ''
+ '%s %s ' % (
+ _('Edit'), _('Configure this form'))
+ '%s %s ' % (
+ _('Fields'), _('Configure the fields of this form'))
+ ' '
+
+ def _q_lookup(self, component):
+ if component == 'fields':
+ return FieldsDirectory(self.form_prefill)
+
+ def edit [html] (self):
+ form = self.form_ui.form_edit()
+ if form.get_widget('cancel').parse():
+ return redirect('.')
+
+ if form.is_submitted() and not form.has_errors():
+ self.form_ui.submit_edit(form)
+ return redirect('.')
+
+ get_response().breadcrumb.append( ('edit', _('Edit')) )
+ html_top('edit', title = _('Edit'))
+ '%s ' % _('Edit')
+
+ form.render()
+
+ def delete [html] (self):
+ form = Form(enctype='multipart/form-data')
+ form.widgets.append(HtmlWidget('%s
' % _(
+ 'You are about to irrevocably delete this form.')))
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ if form.get_widget('cancel').parse():
+ return redirect('..')
+ if not form.is_submitted() or form.has_errors():
+ get_response().breadcrumb.append(('delete', _('Delete')))
+ html_top('delete_form', title = _('Delete Form'))
+ '%s : %s ' % (_('Delete Form'), self.form_prefill.id)
+ form.render()
+ else:
+ self.form_prefill.remove_self()
+ return redirect('..')
+
+
+class FormsDirectory(Directory):
+ _q_exports = ['', 'new']
+
+ def __init__(self, host):
+ get_response().breadcrumb.append(('forms_prefill/', _('Forms')))
+ self.host = host
+
+ def _q_lookup(self, component):
+ return FormPage(component)
+
+ def _q_index [html] (self):
+ html_top('forms', title = _('Forms'))
+ """""" % _('New Form')
+
+ ''
+
+ def new [html] (self):
+ get_response().breadcrumb.append(('new', _('New')) )
+ form_prefill = FormPrefill()
+ form_prefill.host_id = self.host.id
+ form_prefill.store()
+ return redirect('%s/edit' % form_prefill.id)
+
diff --git a/larpe/tags/release-1.1.1/larpe/admin/hosts.ptl b/larpe/tags/release-1.1.1/larpe/admin/hosts.ptl
new file mode 100644
index 0000000..db38874
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/hosts.ptl
@@ -0,0 +1,1352 @@
+import os
+import urllib
+import urlparse
+
+from quixote import redirect, get_request, get_response, get_publisher
+from quixote.directory import Directory
+
+import lasso
+
+from qommon.admin.menu import html_top, command_icon
+from qommon import get_cfg
+from qommon.form import *
+from qommon.misc import http_get_page, get_abs_path
+
+from larpe import site_authentication
+from larpe import errors
+from larpe import misc
+from larpe.hosts import Host
+from larpe.admin.apache import Location
+from larpe.admin.liberty_utils import *
+from larpe.admin.apache import write_apache2_vhosts
+from larpe.admin.forms_prefill import FormsDirectory
+from larpe.Defaults import DATA_DIR
+from larpe.plugins import site_authentication_plugins
+
+def check_basic_configuration(form):
+ get_publisher().reload_cfg()
+ # Check reversed_hostname and reversed_directory
+ reversed_hostname = form.get_widget('reversed_hostname').parse()
+ reversed_directory = form.get_widget('reversed_directory').parse()
+ if reversed_hostname == get_publisher().cfg['proxy_hostname'] and not reversed_directory:
+ form.set_error('reversed_hostname',
+ _('You must either choose a different hostname from Larpe or specify a reversed directory'))
+
+def convert_label_to_name(label):
+ '''Build host name from host label'''
+ name = label.lower()
+ invalid_characters = [' ', "'"]
+ for char in invalid_characters:
+ name = name.replace(char, '_')
+ return name
+
+class DictWidget(Widget):
+ def render_content [html] (self):
+ self.render_br = False
+ if self.value['enabled'] is True:
+ htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled', checked='checked')
+ else:
+ htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled')
+ ' ' + self.name + ' '
+ htmltag('input', xml_end=True, type='text', name=self.name, value=self.value['value'], size='35', **self.attrs)
+
+ def _parse(self, request):
+ enabled = request.form.get(self.name + '_enabled')
+ value = request.form.get(self.name)
+ self.value = { 'enabled': enabled, 'value': value }
+
+
+class ConfigurationAssistant(Directory):
+ _q_exports = ['start', 'check_new_address', 'modify_site_address_and_name',
+ 'authentication_and_logout_adresses', 'check_auto_detected_configuration',
+ 'credentials', 'send_authentication_request', 'check_authentication',
+ 'see_authentication_response', 'see_response_html_page',
+ 'see_bad_response_html_page', 'authentication_success_criteria',
+ 'modify_authentication_request', 'auth_request_post_parameters',
+ 'auth_request_http_headers', 'sso_init_link', 'metadatas',
+ 'check_full_configuration', 'advanced_options']
+
+ def __init__(self, host):
+ self.host = host
+
+ def html_top [html] (self, title):
+ html_top('hosts', title)
+ '%s ' % title
+
+ def start [html] (self):
+ # Check the global domain name has been previously set
+ get_publisher().reload_cfg()
+ if not get_cfg('domain_names') or not get_cfg('domain_names')[0]:
+ get_response().breadcrumb.append(('start', _('Basic configuration')))
+ self.html_top(_('Need domain name configuration'))
+ return htmltext(_('''Before configuring hosts, you must
+setup a global domain name in
+%(settings)s menu.''') % { 'settings': _('Settings') })
+
+ form = self.form_start()
+
+ if form.get_widget('cancel').parse():
+ return redirect('../..')
+
+ connection_failure = None
+ if form.is_submitted() and not form.has_errors():
+ try:
+ self.submit_start_form(form)
+ except Exception, e:
+ connection_failure = e
+ else:
+ if form.get_widget('terminate').parse():
+ return redirect('..')
+ return redirect('check_new_address')
+
+ get_response().breadcrumb.append(('start', _('Basic configuration')))
+ self.html_top(_('Step 1 - Basic configuration'))
+
+ if connection_failure:
+ '%s
' % connection_failure
+
+ form.render()
+
+ def form_start(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(UrlWidget, 'orig_site', title = _('Original site root address'), required = True,
+ size = 50, value = self.host.orig_site,
+ hint = _('If your site address is http://test.org/index.php, put http://test.org/ here'))
+ get_publisher().reload_cfg()
+ if get_cfg('proxy', {}).get('enabled'):
+ form.add(CheckboxWidget, 'use_proxy', title = _('Use a proxy'),
+ hint = _("Uncheck it if Larpe doesn't need to use the proxy to connect to this site"),
+ value = self.host.use_proxy)
+ else:
+ form.add(HtmlWidget, htmltext('%s
' % \
+ _('''If Larpe needs to use a proxy to connect to this site, you must first configure
+ it in global proxy parameters .''')))
+ form.add_submit('cancel', _('Cancel'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+ return form
+
+ def submit_start_form(self, form):
+ fields = ['orig_site']
+ use_proxy = get_cfg('proxy', {}).get('enabled')
+ if use_proxy:
+ fields += ['use_proxy']
+ for f in fields:
+ setattr(self.host, f, form.get_widget(f).parse())
+
+ # If no proxy is setup yet, set use_proxy to False for new hosts in case a proxy is later configured
+ if not use_proxy:
+ self.host.use_proxy = False
+
+ # Remove what is after the last '/'
+ #self.host.orig_site = '/'.join(self.host.orig_site.split('/')[:-1])
+
+ html_page = self.get_data_after_redirects(self.host.orig_site)
+
+ if not self.host.label:
+ # Look for html title in original site index page
+ regexp = re.compile("""(.*?)""", re.DOTALL | re.IGNORECASE)
+ title = regexp.findall(html_page)
+ if title:
+ self.host.label = title[0]
+ else:
+ self.host.label = 'Untitled'
+ # If another site already uses this site title, add trailings "_" until we find an available name
+ existing_label = True
+ while existing_label is True:
+ for any_host in Host.select():
+ if any_host.id != self.host.id and self.host.label == any_host.label:
+ self.host.label += '_'
+ break
+ else:
+ existing_label = False
+
+ # Fill host.name attribute
+ self.host.name = convert_label_to_name(self.host.label)
+
+ if not self.host.scheme:
+ # Get tokens from orig site url
+ orig_scheme, rest = urllib.splittype(self.host.orig_site)
+ orig_host, rest = urllib.splithost(rest)
+
+ get_publisher().reload_cfg()
+ # Set url scheme (HTTP or HTTPS)
+ # TODO: Handle the option "Both"
+ if get_cfg('sites_url_scheme'):
+ self.host.scheme = get_cfg('sites_url_scheme')
+ else:
+ self.host.scheme = orig_scheme
+
+ if not self.host.reversed_hostname:
+ # Build a new domain name
+ short_name = orig_host.split('.')[0]
+ if short_name == 'www':
+ short_name = orig_host.split('.')[1]
+ self.host.reversed_hostname = '%s.%s' % (short_name, get_cfg('domain_names')[0])
+ # If another site already uses this domain name, add some trailing "_" until we find an available name
+ existing_domain = True
+ while existing_domain is True:
+ for any_host in Host.select():
+ if any_host.id != self.host.id and self.host.reversed_hostname == any_host.reversed_hostname:
+ self.host.reversed_hostname += '-'
+ break
+ else:
+ existing_domain = False
+ self.host.reversed_directory = None
+
+ if not self.host.new_url:
+ # New url for this host
+ self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
+ # FIXME: Check if the new domain name already exists
+
+ # New url for this host
+ # self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
+ # if self.host.reversed_directory is not None:
+ # self.host.new_url += '%s/' % self.host.reversed_directory
+
+ self.host.store()
+ write_apache2_vhosts()
+
+ # XXX: Should use the FancyURLopener class instead when it supports proxies
+ def get_data_after_redirects(self, start_url):
+ if not start_url:
+ return ''
+ status = 302
+ location = None
+ while status // 100 == 3:
+ if location is None:
+ url = start_url
+ elif location.startswith('http'):
+ # Location is an absolute path
+ url = location
+ else:
+ # Location is a relative path
+ url = urlparse.urljoin(start_url, location)
+ response, status, data, auth_headers = http_get_page(url, use_proxy=self.host.use_proxy)
+ location = response.getheader('Location', None)
+ return data
+
+ def create_dirs(self):
+ # Hack : sites must use the configuration which is stored in main Larpe directory,
+ # but they need to have a directory named with their hostname, which will contain the
+ # main domain name for Larpe so they know where is the main configuration
+ hostname_dir = get_abs_path(os.path.join('..', self.host.reversed_hostname))
+ if not os.path.exists(hostname_dir):
+ os.mkdir(hostname_dir)
+ # Load the configuration from the main directory
+ get_publisher().reload_cfg()
+ # Write it in the site directory
+ get_publisher().write_cfg(hostname_dir)
+
+ # Storage directories
+ if not self.host.reversed_directory:
+ reversed_dir = 'default'
+ else:
+ reversed_dir = self.host.reversed_directory
+ self.host.site_dir = \
+ os.path.join(get_publisher().app_dir, 'sp', self.host.reversed_hostname, reversed_dir)
+ user_dir = os.path.join(self.host.site_dir, 'users')
+ token_dir = os.path.join(self.host.site_dir, 'tokens')
+ filter_dir = os.path.join(self.host.site_dir, 'filters')
+ for dir in (self.host.site_dir, user_dir, token_dir, filter_dir):
+ if not os.path.isdir(dir):
+ os.makedirs(dir)
+
+ def generate_ssl_keys(self):
+ # Generate SSL keys
+ private_key_path = os.path.join(self.host.site_dir, 'private_key.pem')
+ public_key_path = os.path.join(self.host.site_dir, 'public_key.pem')
+ if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
+ set_provider_keys(private_key_path, public_key_path)
+ self.host.private_key = private_key_path
+ self.host.public_key = public_key_path
+
+ def generate_metadatas(self):
+ metadata_cfg = {}
+
+ # Organization name
+ self.host.organization_name = self.host.label
+ metadata_cfg['organization_name'] = self.host.organization_name
+
+ # Base URL
+ base_url = '%s://%s%s/liberty/%s/liberty' % (self.host.scheme,
+ self.host.reversed_hostname,
+ get_request().environ['SCRIPT_NAME'],
+ self.host.name)
+ metadata_cfg['base_url'] = base_url
+ self.host.base_url = base_url
+
+ if lasso.SAML2_SUPPORT:
+ saml2_base_url = '%s://%s%s/liberty/%s/saml' % (self.host.scheme,
+ self.host.reversed_hostname,
+ get_request().environ['SCRIPT_NAME'],
+ self.host.name)
+ metadata_cfg['saml2_base_url'] = saml2_base_url
+ self.host.saml2_base_url = saml2_base_url
+
+ # Provider Id
+ provider_id = '%s/metadata' % base_url
+ metadata_cfg['provider_id'] = provider_id
+ self.host.provider_id = provider_id
+
+ if lasso.SAML2_SUPPORT:
+ saml2_provider_id = '%s/metadata' % saml2_base_url
+ metadata_cfg['saml2_provider_id'] = saml2_provider_id
+ self.host.saml2_provider_id = saml2_provider_id
+
+ # Read public key
+ public_key = ''
+ if self.host.public_key is not None and os.path.exists(self.host.public_key):
+ metadata_cfg['signing_public_key'] = open(self.host.public_key).read()
+
+ # Write metadatas
+ metadata_path = os.path.join(self.host.site_dir, 'metadata.xml')
+ open(metadata_path, 'w').write(get_metadata(metadata_cfg))
+ self.host.metadata = metadata_path
+
+ if lasso.SAML2_SUPPORT:
+ saml2_metadata_path = os.path.join(self.host.site_dir, 'saml2_metadata.xml')
+ open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
+ self.host.saml2_metadata = saml2_metadata_path
+
+ def check_new_address [html] (self):
+ form = Form(enctype='multipart/form-data')
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+
+ if form.get_widget('cancel').parse():
+ return redirect('start')
+
+ if form.is_submitted():
+ self.create_dirs()
+ if self.host.private_key is None:
+ self.generate_ssl_keys()
+ self.generate_metadatas()
+ self.host.store()
+
+ if form.get_widget('terminate').parse():
+ return redirect('..')
+ return redirect('authentication_and_logout_adresses')
+
+ get_response().breadcrumb.append(('check_new_address', _('Check site address and name')))
+ self.html_top(_('Step 2 - Check the new site address works'))
+
+ '%s ' % _('DNS configuration')
+
+ '%s
' % _('''Before opening the following link, ensure you have configured your DNS
+for this address. If you don't have a DNS server and you just want to test Larpe, add this
+domain name in the file "/etc/hosts".''')
+
+ '%s
' % _('''Then you can open this link in a new window or tab and see if your site
+is displayed. If it's ok, you can click the "%(next)s" button. Otherwise, click the "%(previous)s"
+button and check your settings.''') % {'next': _('Next'), 'previous': _('Previous')}
+
+ '%s ' % _('Site adress and name')
+
+ '%s' % _('The new address of this site is ')
+ '%s ' % (self.host.new_url, self.host.new_url)
+ '%s
' % _('The name of this site is "%s".') % self.host.label
+ '%s
' % htmltext(_('''You can also
+modify the address or the name of this site '''))
+
+ form.render()
+
+ def modify_site_address_and_name [html] (self):
+ form = self.form_modify_site_address_and_name()
+
+ if form.get_widget('cancel').parse():
+ return redirect('check_new_address')
+
+ if form.is_submitted() and not form.has_errors():
+ label = form.get_widget('label').parse()
+ name = convert_label_to_name(label)
+ for any_host in Host.select():
+ if any_host.id != self.host.id and name == any_host.name:
+ form.set_error('label', _('An host with the same name already exists'))
+ break
+
+ if form.is_submitted() and not form.has_errors():
+ self.submit_modify_site_address_and_name_form(form)
+ return redirect('check_new_address')
+
+ get_response().breadcrumb.append(('modify_site_address_and_name', _('Modify site address and name')))
+ self.html_top(_('Modify site address and name'))
+
+ form.render()
+
+ def form_modify_site_address_and_name(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(UrlWidget, 'new_url', title = _('Address'), required = True,
+ size = 50, value = self.host.new_url)
+ form.add(StringWidget, 'label', title = _('Name'), required = True,
+ size = 50, value = self.host.label)
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ return form
+
+ def submit_modify_site_address_and_name_form(self, form):
+ fields = ['new_url', 'label']
+ for f in fields:
+ setattr(self.host, f, form.get_widget(f).parse())
+
+ # Split url to retrieve components
+ tokens = urlparse.urlparse(self.host.new_url)
+ self.host.scheme = tokens[0]
+ self.host.reversed_hostname = tokens[1]
+ self.host.reversed_directory = tokens[2]
+ if self.host.reversed_directory.startswith('/'):
+ self.host.reversed_directory = self.host.reversed_directory[1:]
+
+ # Fill host.name attribute
+ self.host.name = convert_label_to_name(self.host.label)
+
+ self.host.store()
+ write_apache2_vhosts()
+
+ def authentication_and_logout_adresses [html] (self):
+ form = self.form_authentication_and_logout_adresses()
+
+ if form.get_widget('cancel').parse():
+ return redirect('check_new_address')
+
+ if form.is_submitted() and not form.has_errors():
+ self.submit_authentication_and_logout_adresses_form(form)
+ if form.get_widget('terminate').parse():
+ return redirect('..')
+ return redirect('check_auto_detected_configuration')
+
+ get_response().breadcrumb.append(('authentication_and_logout_adresses', _('Authentication and logout')))
+ self.html_top(_('Step 3 - Configure authentication and logout pages'))
+
+ form.render()
+
+ def form_authentication_and_logout_adresses(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(ValidUrlWidget, 'auth_url', title = _('Authentication form page address'),
+ hint = _('Address of a page on the site which contains the authentication form'),
+ required = True, size = 50, value = self.host.auth_url)
+ form.add(ValidUrlWidget, 'logout_url', title = _('Logout address'), required = False,
+ hint = _('Address of the logout link on the site'),
+ size = 50, value = self.host.logout_url)
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+ return form
+
+ def submit_authentication_and_logout_adresses_form(self, form):
+ fields = ['auth_url', 'logout_url']
+ for f in fields:
+ setattr(self.host, f, form.get_widget(f).parse())
+ self.host.auth_form_url = self.host.auth_url
+
+ if not self.host.http_headers:
+ self.host.http_headers = {
+ 'Content-Type': { 'enabled': True, 'value': 'application/x-www-form-urlencoded', 'immutable': False },
+ 'X-Forwarded-For': { 'enabled': True, 'value': _('(computed automatically)'), 'immutable': True },
+ 'X-Forwarded-Host': { 'enabled': True, 'value': self.host.reversed_hostname, 'immutable': False },
+ }
+
+ self.auto_detect_configuration()
+ self.host.store()
+ write_apache2_vhosts()
+
+ def check_auto_detected_configuration [html] (self):
+ plugins_name = site_authentication_plugins.get_plugins_name()
+ plugins_name.append(None)
+ plugins_name.sort()
+ form = Form(enctype='multipart/form-data')
+ form.add(SingleSelectWidget, 'plugin', title = _('Plugin'), required = False,
+ hint = _('You can force a plugin'),
+ value = self.host.site_authentication_plugin,
+ options = plugins_name)
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+
+ if form.get_widget('cancel').parse():
+ return redirect('authentication_and_logout_adresses')
+
+ if form.is_submitted():
+ self.host.site_authentication_plugin = form.get_widget('plugin').parse()
+ self.host.store()
+ if form.get_widget('terminate').parse():
+ write_apache2_vhosts()
+ return redirect('..')
+ return redirect('credentials')
+
+ get_response().breadcrumb.append(('check_auto_detected_configuration', _('Auto detected configuration')))
+ self.html_top(_('Step 4 - Check automatically detected configuration for the authentication form'))
+
+ host_attrs = (
+ ('auth_check_url', _('Address where the authentication form must be sent')),
+ ('login_field_name', _('Name of the login field')),
+ ('password_field_name', _('Name of the password field')),
+ )
+
+ html_fields = ''
+ success = True
+ for attr, name in host_attrs:
+ color = 'black'
+ if attr in ('auth_check_url', 'login_field_name', 'password_field_name') and \
+ not getattr(self.host, str(attr)):
+ color = 'red'
+ success = False
+ html_fields += '%s
' % (color, name)
+ html_fields += '%s
' % \
+ (color, getattr(self.host, str(attr)))
+ if getattr(self.host, str(attr)) == '':
+ html_fields += ' '
+
+ ''
+ if success:
+ htmltext(_('''\
+The following authentication form parameters have been detected. If they look right, you can go to the next step.
+If you think they are wrong, go back and check your settings then try again.
+'''))
+ else:
+ htmltext(_('''\
+The following authentication form parameters in red haven't been correctly detected. Go back and check
+your settings then try again.
+'''))
+ '
'
+
+ html_fields
+ form.render()
+
+ def credentials [html] (self):
+ form = self.form_credentials()
+
+ if form.get_widget('cancel').parse():
+ return redirect('check_auto_detected_configuration')
+
+ if form.is_submitted() and not form.has_errors():
+ self.submit_credentials_form(form)
+ if form.get_widget('terminate').parse():
+ return redirect('..')
+ return redirect('send_authentication_request')
+
+ get_response().breadcrumb.append(('credentials', _('Credentials')))
+ self.html_top(_('Step 5 - Fill in a valid username/password for this site'))
+ form.render()
+
+ def form_credentials(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(StringWidget, 'username', title = _('Username'), required = True,
+ size = 30, value = self.host.valid_username)
+ form.add(PasswordWidget, 'password', title = _('Password'), required = True,
+ size = 30, value = self.host.valid_password)
+ for name, values in self.host.select_fields.iteritems():
+ options = []
+ if values:
+ for value in values:
+ options.append(value)
+ form.add(SingleSelectWidget, name, title = name.capitalize(),
+ value = values[0], options = options)
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+ return form
+
+ def submit_credentials_form(self, form):
+ self.host.valid_username = form.get_widget('username').parse()
+ self.host.valid_password = form.get_widget('password').parse()
+ self.host.valid_select = {}
+ for name, values in self.host.select_fields.iteritems():
+ if form.get_widget(name):
+ self.host.valid_select[name] = form.get_widget(name).parse()
+
+ self.host.store()
+
+ def send_authentication_request(self):
+ site_auth = site_authentication.get_site_authentication(self.host)
+
+ # Request with good credentials
+ self.host.auth_request_status, self.host.auth_request_data = site_auth.local_auth_check_dispatch(
+ self.host.valid_username, self.host.valid_password, self.host.valid_select)
+ self.host.auth_request_success, self.host.auth_request_return_content = \
+ site_auth.check_auth(self.host.auth_request_status, self.host.auth_request_data)
+
+ # Request with bad credentials
+ self.host.auth_bad_request_status, self.host.auth_bad_request_data = site_auth.local_auth_check_dispatch(
+ 'this_is_a_bad_login', 'this_is_a_bad_password', {})
+ self.host.auth_bad_request_success, self.host.auth_bad_request_return_content = \
+ site_auth.check_auth(self.host.auth_bad_request_status, self.host.auth_bad_request_data)
+
+ self.host.store()
+
+ return redirect('check_authentication')
+
+ def check_authentication [html] (self):
+ form = Form(enctype='multipart/form-data')
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+
+ if form.get_widget('cancel').parse():
+ return redirect('credentials')
+
+ if form.is_submitted():
+ if form.get_widget('terminate').parse():
+ return redirect('..')
+ return redirect('sso_init_link')
+
+ get_response().breadcrumb.append(('check_authentication', _('Check authentication')))
+ self.html_top(_('Step 6 - Check the authentication process'))
+
+ if self.host.auth_request_success:
+ good_cred_status = 'Success [OK] '
+ else:
+ good_cred_status = 'Failed [KO] [OK]'
+ else:
+ bad_cred_status = 'Success [KO] '
+
+ 'Results of authentication requests :
\n'
+ '\n'
+ '\tWith good credentials : %s ' % good_cred_status
+ '\tWith bad credentials : %s ' % bad_cred_status
+ ' \n'
+
+ if self.host.auth_request_success and not self.host.auth_bad_request_success :
+ '%s
\n' % _('Authentication succeeded ! You can go to the next step.')
+ else:
+ '%s
\n' % _('Authentication has failed. To resolve this problem, you can :')
+ '\n'
+ '\t%s \n' % \
+ _('Try authentication again')
+ '\t%s \n' % \
+ _('See the response of the authentication requests')
+ '\t%s \n' % \
+ _('Modify the parameters of the authentication requests')
+ '\t%s \n' % \
+ _('Change the way Larpe detects the authentication is successful or not')
+ '\t%s \n' % _('Go back and change your username and/or password')
+ ' \n'
+
+ form.render()
+
+ def see_authentication_response [html] (self):
+ get_response().breadcrumb.append(('see_authentication_response', _('Authentication response')))
+ self.html_top(_('Authentication response'))
+
+ '%s ' % _('Response of the request with good credentials')
+
+ '%s
' % \
+ str(_('HTTP status code'))
+ '%s (%s)
' % \
+ (self.host.auth_request_status, status_reasons[self.host.auth_request_status])
+
+ ''
+ '%s ' % _('See HTML page')
+ ' '
+
+ '%s ' % _('Response of the request with bad credentials')
+
+ '%s
' % \
+ str(_('HTTP status code'))
+ '%s (%s)
' % \
+ (self.host.auth_bad_request_status, status_reasons[self.host.auth_bad_request_status])
+
+ ''
+ '%s ' % _('See HTML page')
+ ' '
+
+ ' ' % _('Back')
+
+ def see_response_html_page (self):
+ return self.host.auth_request_data
+
+ def see_bad_response_html_page (self):
+ return self.host.auth_bad_request_data
+
+ def authentication_success_criteria [html] (self):
+ form = self.form_authentication_success_criteria()
+
+ if form.get_widget('cancel').parse():
+ return redirect('check_authentication')
+
+ if form.is_submitted() and not form.has_errors():
+ self.submit_authentication_success_criteria_form(form)
+ return redirect('check_authentication')
+
+ get_response().breadcrumb.append(('authentication_success_criteria', _('Authentication success criteria')))
+ self.html_top(_('Authentication success criteria'))
+
+ form.render()
+
+ def form_authentication_success_criteria(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(RadiobuttonsWidget, 'auth_system', title = _('Authentication system of the original site'),
+ options=[
+ ('password', _('Check the existence of a password field'), 'password'),
+ ('match_text', _('Match some text to detect an authentication failure'), 'match_text'),
+ ],
+ sort=False,
+ delim=htmltext(' '),
+ value = self.host.auth_system)
+ form.add(RegexStringWidget, 'auth_match_text', title = _('Text to match in case of authentication failure'),
+ required = False, size = 50, value = self.host.auth_match_text)
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ return form
+
+ def submit_authentication_success_criteria_form(self, form):
+ for f in ('auth_system', 'auth_match_text'):
+ value = form.get_widget(f).parse()
+ setattr(self.host, f, value)
+
+ self.host.store()
+
+ def modify_authentication_request [html] (self):
+ get_response().breadcrumb.append(('modify_authentication_request', _('Authentication request')))
+ self.html_top(_('Modify the parameters of the authentication requests'))
+
+ ''
+ '%s %s ' % (
+ _('Modify POST parameters'), _('Configure the form attributes that will be sent within the authentication POST requests'))
+ '%s %s ' % (
+ _('Modify HTTP headers'), _('Configure the HTTP headers of the authentication requests made by Larpe'))
+ ' '
+ ' ' % _('Back')
+
+ def auth_request_post_parameters [html] (self):
+ form = self.form_auth_request_post_parameters()
+
+ if form.get_widget('cancel').parse():
+ return redirect('modify_authentication_request')
+
+ if form.is_submitted() and not form.has_errors():
+ self.submit_auth_request_post_parameters_form(form)
+ return redirect('modify_authentication_request')
+
+ get_response().breadcrumb.append(('auth_request_post_parameters', _('POST parameters')))
+ self.html_top(_('Configure POST parameters'))
+
+ '%s
' % _('''Here are the detected form fields that will be sent as parameters of the
+authentication POST request. You can desactivate some or all of them, or change their value.''')
+
+ form.render()
+
+ def form_auth_request_post_parameters(self):
+ form = Form(enctype='multipart/form-data')
+ for name, value in self.host.post_parameters.iteritems():
+ if value['immutable']:
+ form.add(DictWidget, name, value, disabled = 'disabled')
+ else:
+ form.add(DictWidget, name, value)
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ return form
+
+ def submit_auth_request_post_parameters_form(self, form):
+ for name, old_value in self.host.post_parameters.iteritems():
+ value = form.get_widget(name).parse()
+ if value['enabled'] == 'on':
+ old_value['enabled'] = True
+ else:
+ old_value['enabled'] = False
+ if old_value['immutable'] is False:
+ old_value['value'] = value['value']
+ self.host.post_parameters[name] = old_value
+ self.host.store()
+
+ def auth_request_http_headers [html] (self):
+ form = self.form_auth_request_http_headers()
+
+ if form.get_widget('cancel').parse():
+ return redirect('modify_authentication_request')
+
+ if form.is_submitted() and not form.has_errors():
+ self.submit_auth_request_http_headers_form(form)
+ return redirect('modify_authentication_request')
+
+ get_response().breadcrumb.append(('auth_request_http_headers', _('HTTP headers')))
+ self.html_top(_('Configure HTTP headers'))
+
+ '%s
' % _('''Here are the HTTP headers that will be sent within the authentication
+POST request. You can desactivate some or all of them, or change their value.''')
+
+ form.render()
+
+ def form_auth_request_http_headers(self):
+ form = Form(enctype='multipart/form-data')
+ for name, value in self.host.http_headers.iteritems():
+ if value['immutable']:
+ form.add(DictWidget, name, value, disabled = 'disabled')
+ else:
+ form.add(DictWidget, name, value)
+ form.add(HtmlWidget, htmltext('%s
' % \
+ _('The headers "Host", "Accept-Encoding" and "Content-Length" will also automatically be sent.')))
+ if get_cfg('proxy', {}).get('enabled') and self.host.use_proxy:
+ form.add(HtmlWidget, htmltext('%s
' % \
+ _('''As Larpe uses a proxy for this site, the headers "Proxy-Authorization",
+"Proxy-Connection" and "Keep-Alive" will be sent as well.''')))
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ return form
+
+ def submit_auth_request_http_headers_form(self, form):
+ for name, old_value in self.host.http_headers.iteritems():
+ value = form.get_widget(name).parse()
+ if value['enabled'] == 'on':
+ old_value['enabled'] = True
+ else:
+ old_value['enabled'] = False
+ if old_value['immutable'] is False:
+ old_value['value'] = value['value']
+ self.host.http_headers[name] = old_value
+ self.host.store()
+
+ def generate_apache_filters(self):
+ self.host.apache_python_paths = []
+ self.host.apache_output_python_filters = []
+ site_auth = site_authentication.get_site_authentication(self.host)
+ output_filters = site_auth.output_filters
+ replace_login_form = self.host.auth_form_places == 'form_everywhere' and \
+ self.host.auth_form_action
+ if replace_login_form and not 'output_replace_form' in output_filters:
+ output_filters.append('output_replace_form')
+ if output_filters:
+ location = Location(self.host)
+ conf = { 'auth_form_action': self.host.auth_form_action,
+ 'name': self.host.name,
+ 'larpe_dir': get_publisher().app_dir,
+ 'logout_url': location.new_logout_url,
+ 'login_url': location.new_auth_url }
+ # Set Python filter path for Apache configuration
+ python_path = os.path.join(self.host.site_dir, 'filters')
+ if python_path not in self.host.apache_python_paths:
+ self.host.apache_python_paths.append(python_path)
+
+ for filter in output_filters:
+ python_file = open(
+ os.path.join(self.host.site_dir, 'filters', filter + ".py"),
+ 'w')
+ python_file.write(
+ open(os.path.join(DATA_DIR, "filters", filter + ".py")).read() % conf
+ )
+ if not filter in self.host.apache_output_python_filters:
+ self.host.apache_output_python_filters.append(filter)
+
+ def sso_init_link [html] (self):
+ form = self.form_sso_init_link()
+
+ if form.get_widget('cancel').parse():
+ return redirect('check_authentication')
+
+ if form.is_submitted() and not form.has_errors():
+ self.submit_sso_init_link_form(form)
+ if form.get_widget('terminate').parse():
+ return redirect('..')
+ return redirect('metadatas')
+
+ get_response().breadcrumb.append(('sso_init_link', _('SSO initiation')))
+ self.html_top(_('Step 7 - Configure how a Single Sign On can be initiated'))
+
+ '%s\n' % _('Most sites use one of the following 2 ways to allow users to initialise an authentication :')
+ '\t
\n'
+ '\t\t%s \n' % \
+ _('''The site has a single authentication page. It redirects users to this page when
+they click a "Login" button or try to access a page which require users to be authenticated.''')
+ '\t\t%s \n' % \
+ _('''The site includes an authentication form in most or all of his pages. Users can
+authenticate on any of these pages, and don't need to be redirected to a separate authentication page.''')
+ '\t \n'
+ '
\n'
+
+ '%s
' % _('Select the way your site works :')
+
+ form.render()
+
+ def form_sso_init_link(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(RadiobuttonsWidget, 'auth_form_places',
+ options=[
+ ('form_once', _('The site has a single authentication page'), 'form_once'),
+ ('form_everywhere', _('The site includes an authentication form in most or all pages'), 'form_everywhere'),
+ ],
+ sort=False, required = True, delim=htmltext(' '), value = self.host.auth_form_places)
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+ return form
+
+ def submit_sso_init_link_form(self, form):
+ fields = [ 'auth_form_places', ]
+ for f in fields:
+ setattr(self.host, f, form.get_widget(f).parse())
+ self.host.auth_form_url = self.host.auth_url
+ self.generate_apache_filters()
+ self.host.store()
+ write_apache2_vhosts()
+
+ def metadatas [html] (self):
+ form = Form(enctype='multipart/form-data')
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+
+ if form.get_widget('cancel').parse():
+ return redirect('sso_init_link')
+
+ if form.is_submitted():
+ if form.get_widget('terminate').parse():
+ return redirect('..')
+ return redirect('advanced_options')
+
+ get_response().breadcrumb.append(('metadatas', _('Metadatas')))
+ self.html_top(_('Step 8 - Metadatas of %(site_name)s' % {'site_name': self.host.name}))
+
+ '%s
' % \
+ _('''Download the metadatas and the public key for this site and
+upload them on your identity provider in order to use Liberty Alliance features.''')
+
+ ''
+ if hasattr(self.host, str('base_url')):
+ if lasso.SAML2_SUPPORT:
+ saml2_metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
+ '%s %s ' % (
+ saml2_metadata_url,
+ _('SAML 2.0 Metadata'),
+ _('Download SAML 2.0 metadata file'))
+ metadata_url = '%s/metadata.xml' % self.host.base_url
+ '%s %s ' % (
+ metadata_url,
+ _('ID-FF 1.2 Metadata'),
+ _('Download ID-FF 1.2 metadata file'))
+ else:
+ '%s
' % _('No metadata has been generated for this host.')
+
+ if hasattr(self.host, str('base_url')) and self.host.public_key and os.path.exists(self.host.public_key):
+ public_key_url = '%s/public_key' % self.host.base_url
+ '%s %s ' % (
+ public_key_url,
+ _('Public key'),
+ _('Download SSL Public Key file'))
+ else:
+ '%s
' % _('No public key has been generated for this host.')
+ ' '
+
+ form.render()
+
+ def advanced_options [html] (self):
+ form = self.form_advanced_options()
+
+ if form.get_widget('cancel').parse():
+ return redirect('metadatas')
+
+ if not form.is_submitted() or form.has_errors():
+ get_response().breadcrumb.append(('advanced_options', _('Advanced options')))
+ self.html_top(_('Step 9 - Advanced options'))
+
+ '%s
' % _('Configure advanced options to setup the last details of your site.')
+ '%s
' % _('''If you don't know what to configure here, just click %(next)s and
+come here later if needed.''') % {'next': _('Next')}
+
+ form.render()
+ else:
+ self.submit_advanced_options_form(form)
+ if form.get_widget('terminate').parse():
+ return redirect('..')
+ return redirect('check_full_configuration')
+
+ def form_advanced_options(self):
+ form = Form(enctype='multipart/form-data')
+ form.add(CheckboxWidget, 'redirect_root_to_login',
+ title=_('Redirect the root URL of the site to the login page.'),
+ value = self.host.redirect_root_to_login)
+ form.add(UrlOrAbsPathWidget, 'return_url', title = _('Return address'),
+ hint = _('Where the user will be redirected after a successful authentication'),
+ required = False, size = 50, value = self.host.return_url)
+ form.add(UrlOrAbsPathWidget, 'root_url', title = _('Error address'),
+ hint = _('Where the user will be redirected after a disconnection or an error'),
+ required = False, size = 50, value = self.host.root_url)
+ form.add(UrlOrAbsPathWidget, 'initiate_sso_url', title = _('URL which must initiate the SSO'),
+ hint = _('''Address which must initiate the SSO. If empty, defaults to the previously
+specified "%s"''') % _('Authentication form page address'),
+ required = False, size = 50, value = self.host.initiate_sso_url)
+ form.add(CheckboxWidget, 'proxy-html', title = _('Apache HTML proxy'),
+ hint = _('''Converts urls in the HTML pages according to the host new domain name.
+Disabled by default because it makes some sites not work correctly.'''),
+ value = 'proxy-html' in self.host.apache_output_filters)
+
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Next'))
+ form.add_submit('terminate', _('Terminate'))
+ return form
+
+ def submit_advanced_options_form(self, form):
+ old_redirect_root_to_login = self.host.redirect_root_to_login
+
+ for f in ('redirect_root_to_login', 'return_url', 'root_url', 'initiate_sso_url'):
+ value = form.get_widget(f).parse()
+ setattr(self.host, f, value)
+
+ f = 'proxy-html'
+ value = form.get_widget(f).parse()
+ if value is True and f not in self.host.apache_output_filters:
+ self.host.apache_output_filters.append(f)
+ if value is False and f in self.host.apache_output_filters:
+ self.host.apache_output_filters.remove(f)
+
+ self.host.store()
+
+ if self.host.initiate_sso_url or self.host.redirect_root_to_login is not old_redirect_root_to_login:
+ write_apache2_vhosts()
+
+ def check_full_configuration [html] (self):
+ form = Form(enctype='multipart/form-data')
+ form.add_submit('cancel', _('Previous'))
+ form.add_submit('submit', _('Finish'))
+
+ if form.get_widget('cancel').parse():
+ return redirect('advanced_options')
+
+ if form.is_submitted():
+ return redirect('../..')
+
+ get_response().breadcrumb.append(('check_full_configuration', _('Check everything works')))
+ self.html_top(_('Step 10 - Check everything works'))
+
+ '%s
' % \
+ _('''Now you can fully test your site, start from the home page, initiate a
+Single Sign On, federate your identities and do a Single Logout.''')
+
+ '%s' % _('The address of your site is : ')
+ '%s ' % (self.host.new_url, self.host.new_url)
+ '
'
+
+ '%s
' % \
+ _('''If everything works, click the "%(finish)s" button, otherwise you can go
+back and check your settings.''') % { 'finish': _('Finish') }
+
+ form.render()
+
+ def auto_detect_configuration(self):
+ # Reset previous detected values
+ self.host.auth_form = None
+ self.host.auth_check_url = None
+ self.host.login_field_name = None
+ self.host.password_field_name = None
+ if not self.host.post_parameters:
+ self.host.post_parameters = {}
+
+ self.parse_page(self.host.auth_form_url)
+
+ def parse_page(self, page_url):
+ # Get the authentication page
+ try:
+ response, status, page, auth_header = http_get_page(page_url, use_proxy=self.host.use_proxy)
+ except Exception, msg:
+ print msg
+ return
+
+ if page is None:
+ return
+ #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to get page'), self.host.auth_form_url))
+
+ # Default authentication mode
+ self.host.auth_mode = 'form'
+
+ if not self.host.site_authentication_plugin:
+ self.host.site_authentication_plugin = site_authentication_plugins.auto_detect(page)
+ self.parse_frames(page)
+ self.parse_forms(page)
+ if self.host.auth_form is not None:
+ self.parse_form_action()
+ input_fields = self.parse_input_fields()
+ self.parse_login_field(input_fields)
+ self.parse_password_field()
+ self.parse_select_fields()
+ self.parse_other_fields()
+
+ def parse_frames(self, page):
+ '''If there are frames, parse them recursively'''
+ regexp = re.compile("""]*?>""", re.DOTALL | re.IGNORECASE)
+ found_frames = regexp.findall(page)
+ if found_frames:
+ for frame_url in found_frames:
+ if frame_url.startswith('http'):
+ frame_full_url = frame_url
+ else:
+ page_url_tokens = frame_url.split('/')
+ page_url_tokens[-1] = frame_url
+ frame_full_url = '/'.join(page_url_tokens)
+ self.parse_page(frame_full_url)
+
+ def parse_forms(self, page):
+ '''Search for an authentication form'''
+ # Get all forms
+ regexp = re.compile("""""", re.DOTALL | re.IGNORECASE)
+ found_forms = regexp.findall(page)
+ if not found_forms:
+ return
+ #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to find any form'), self.host.auth_form_url))
+
+ # Get the first form with a password field
+ for found_form in found_forms:
+ regexp = re.compile(""" ]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
+ if regexp.search(found_form) is not None:
+ self.host.auth_form = found_form
+ break
+
+ def parse_form_action(self):
+ '''Get the action url of the form'''
+ regexp = re.compile("""].*?>""", re.DOTALL | re.IGNORECASE)
+ self.host.auth_form_action = regexp.findall(self.host.auth_form)[0]
+ # FIXME: Find a Python module which unescapes html entities
+ self.host.auth_check_url = self.host.auth_form_action.replace('&', '&')
+ if not self.host.auth_check_url.startswith('http'):
+ if self.host.auth_check_url.startswith('/'):
+ if self.host.orig_site.startswith('https'):
+ orig_site_root = 'https://%s' % urllib.splithost(self.host.orig_site[6:])[0]
+ else:
+ orig_site_root = 'http://%s' % urllib.splithost(self.host.orig_site[5:])[0]
+ self.host.auth_check_url = orig_site_root + self.host.auth_check_url
+ else:
+ auth_form_url_tokens = self.host.auth_form_url.split('/')
+ auth_form_url_tokens[-1] = self.host.auth_check_url
+ self.host.auth_check_url = '/'.join(auth_form_url_tokens)
+
+ def parse_input_fields(self):
+ '''Get all input fields'''
+ regexp = re.compile(""" ]*?>""", re.DOTALL | re.IGNORECASE)
+ return regexp.findall(self.host.auth_form)
+
+ def parse_login_field(self, input_fields):
+ '''Get login field name'''
+ try:
+ regexp = re.compile(""" ]*?type=["']?text["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
+ text_fields = regexp.findall(self.host.auth_form)
+ login_field = ''
+ if text_fields:
+ login_field = text_fields[0]
+ else:
+ for field in input_fields:
+ if re.search("""type=["']?""", field, re.DOTALL | re.IGNORECASE) is None:
+ login_field = field
+ break
+ regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
+ self.host.login_field_name = regexp.findall(login_field)[0]
+ if not self.host.post_parameters.has_key(self.host.login_field_name):
+ self.host.post_parameters[self.host.login_field_name] = \
+ { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
+ self.host.store()
+ except IndexError, e:
+ self.host.login_field_name = None
+ print 'Error handling login field : %s' % e
+
+ def parse_password_field(self):
+ '''Get password field name'''
+ try:
+ regexp = re.compile(""" ]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
+ password_field = regexp.findall(self.host.auth_form)[0]
+ regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
+ self.host.password_field_name = regexp.findall(password_field)[0]
+ if not self.host.post_parameters.has_key(self.host.password_field_name):
+ self.host.post_parameters[self.host.password_field_name] = \
+ { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
+ except IndexError, e:
+ self.host.password_field_name = None
+ print 'Error handling password field : %s' % e
+
+ def parse_select_fields(self):
+ '''Add select fields to host attributes'''
+ # First added for Imuse (Rennes)
+ regexp = re.compile("""""", re.DOTALL | re.IGNORECASE)
+ self.host.select_fields = {}
+ for field in regexp.findall(self.host.auth_form):
+ try:
+ regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
+ name = regexp.findall(field)[0]
+ regexp = re.compile("""]*?>.*? """, re.DOTALL | re.IGNORECASE)
+ options = regexp.findall(field)
+ values = []
+ for option in options:
+ regexp = re.compile("""]*?>(.*?) """, re.DOTALL | re.IGNORECASE)
+ option_label = regexp.findall(option)
+ regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
+ option_value = regexp.findall(option)
+ if option_label:
+ if not option_value:
+ option_value = option_label
+ values.append((option_value[0], option_label[0]))
+ else:
+ print >> sys.stderr, 'W: Could not parse select options'
+ self.host.select_fields[name] = values
+ if not self.host.post_parameters.has_key(name):
+ self.host.post_parameters[name] = \
+ { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
+ except IndexError, e:
+ continue
+
+ def parse_other_fields(self):
+ '''Get the default value of all other fields'''
+ self.host.other_fields = {}
+
+ # Get hidden fields
+ regexp = re.compile(""" ]*?type=["']?hidden["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
+ other_fields = regexp.findall(self.host.auth_form)
+
+ # Only get first submit field
+ regexp = re.compile(""" ]*?type=["']?submit["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
+ found = regexp.findall(self.host.auth_form)
+ if found:
+ if other_fields:
+ other_fields.append(found[0])
+ else:
+ other_fields = found[0]
+
+ for field in other_fields:
+ try:
+ regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
+ name = regexp.findall(field)[0]
+ regexp = re.compile("""value=["'](.*?)["'][\s/>]""", re.DOTALL | re.IGNORECASE)
+ value = regexp.findall(field)[0]
+ self.host.other_fields[name] = value
+ if not self.host.post_parameters.has_key(name):
+ self.host.post_parameters[name] = { 'enabled': True, 'value': value, 'immutable': False }
+ except IndexError, e:
+ continue
+
+
+class HostPage(Directory):
+ _q_exports = ['', 'delete']
+
+ def __init__(self, host_id):
+ self.host = Host.get(host_id)
+ get_response().breadcrumb.append((host_id + '/', self.host.label))
+
+ def _q_lookup(self, component):
+ if component == 'configuration_assistant':
+ return ConfigurationAssistant(self.host)
+ elif component == 'forms_prefill':
+ return FormsDirectory(self.host)
+
+ def _q_index [html] (self):
+ get_publisher().reload_cfg()
+ html_top('hosts', title = self.host.label)
+
+ '%s ' % _('Configuration assistant')
+
+ ''
+
+ '%s %s ' % (
+ _('Address of the original site'), _('Configure the root address of the site'))
+
+ '%s %s ' % (
+ _('New address and name'), _('Configure the new address and name of this site'))
+
+ '%s %s ' % (
+ _('Authentication and logout addresses'), _('Configure the authentication and logout addresses of the original site'))
+
+ '%s %s ' % (
+ _('Check auto detected configuration'), _('Check the automatically detected configuration is right'))
+
+ '%s %s ' % (
+ _('Credentials'), _('Configure some valid credentials to authenticate on the original site'))
+
+ '%s %s ' % (
+ _('Retry authentication'), _('Retry sending an authentication request to the site to check if your new parameters work well'))
+
+ '%s %s ' % (
+ _('Check authentication response'), _('Check the response from the latest authentication request'))
+
+ '%s %s ' % (
+ _('Configure authentication success criteria'), _('Specify how Larpe knows if the authentication has succeeded or not'))
+
+ '%s %s ' % (
+ _('Modify authentication request'), _('Modify POST fields or HTTP headers of the authentication request'))
+
+ '%s %s ' % (
+ _('Configure how a Single Sign On can be initiated'), _('Configure how a Single Sign On can be initiated'))
+
+ '%s %s ' % (
+ _('Metadatas and key'), _('Download SAML 2.0 or ID-FF metadatas and SSL public key'))
+
+ '%s %s ' % (
+ _('Adavanced options'), _('Configure advanced options to setup the last details of your site'))
+
+ ' '
+
+ '%s ' % _('Form prefilling with ID-WSF')
+
+ ''
+ '%s %s ' % (
+ _('Forms'), _('Configure the forms to prefill'))
+ ' '
+
+ def delete [html] (self):
+ form = Form(enctype='multipart/form-data')
+ form.widgets.append(HtmlWidget('%s
' % _(
+ 'You are about to irrevocably delete this host.')))
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ if form.get_widget('cancel').parse():
+ return redirect('..')
+ if not form.is_submitted() or form.has_errors():
+ get_response().breadcrumb.append(('delete', _('Delete')))
+ html_top('hosts', title = _('Delete Host'))
+ '%s : %s ' % (_('Delete Host'), self.host.label)
+ form.render()
+ else:
+ self.host.remove_self()
+ write_apache2_vhosts()
+ return redirect('..')
+
+
+class HostsDirectory(Directory):
+ _q_exports = ['', 'new']
+
+ def _q_index [html] (self):
+ get_response().breadcrumb.append(('hosts/', _('Hosts')))
+ html_top('hosts', title = _('Hosts'))
+ """""" % _('New Host')
+
+ ''
+
+ def new [html] (self):
+ if not os.path.isdir(os.path.join(get_publisher().app_dir, str('idp'))):
+ html_top('hosts', title = _('New Host'))
+ html = '%s ' % _('New Host')
+ html += 'You must ' % misc.get_root_url()
+ html += 'configure an Identity Provider first '
+ html += ' ' % _('Back')
+ return html
+
+ get_response().breadcrumb.append(('hosts/', _('Hosts')))
+ get_response().breadcrumb.append(('new', _('New')) )
+ host = Host()
+ host.store()
+ return redirect('%s/configuration_assistant/start' % host.id)
+
+ def _q_lookup(self, component):
+ get_response().breadcrumb.append(('hosts/', _('Hosts')))
+ return HostPage(component)
+
diff --git a/larpe/tags/release-1.1.1/larpe/admin/liberty_utils.py b/larpe/tags/release-1.1.1/larpe/admin/liberty_utils.py
new file mode 100644
index 0000000..de324c4
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/liberty_utils.py
@@ -0,0 +1,129 @@
+import os
+
+def set_provider_keys(private_key_path, public_key_path):
+ # use system calls for openssl since PyOpenSSL doesn't expose the
+ # necessary functions.
+ if os.system('openssl version > /dev/null 2>&1') == 0:
+ os.system('openssl genrsa -out %s 2048' % private_key_path)
+ os.system('openssl rsa -in %s -pubout -out %s' % (private_key_path, public_key_path))
+
+
+def get_metadata(cfg):
+ prologue = """\
+
+""" % cfg
+
+ sp_head = """
+ """
+
+ signing_public_key = ''
+ if cfg.has_key('signing_public_key') and cfg['signing_public_key']:
+ if 'CERTIF' in cfg['signing_public_key']:
+ signing_public_key = """
+
+
+ %s
+
+ """ % cfg['signing_public_key']
+ elif 'KEY' in cfg['signing_public_key']:
+ signing_public_key = """
+
+
+ %s
+
+ """ % cfg['signing_public_key']
+
+ sp_body = """
+ %(base_url)s/assertionConsumer
+
+ %(base_url)s/soapEndpoint
+
+ %(base_url)s/singleLogout
+ %(base_url)s/singleLogoutReturn
+ http://projectliberty.org/profiles/slo-idp-http
+ http://projectliberty.org/profiles/slo-sp-soap
+ http://projectliberty.org/profiles/slo-sp-http
+
+ %(base_url)s/federationTermination
+ %(base_url)s/federationTerminationReturn
+ http://projectliberty.org/profiles/fedterm-idp-soap
+ http://projectliberty.org/profiles/fedterm-idp-http
+ http://projectliberty.org/profiles/fedterm-sp-soap
+ http://projectliberty.org/profiles/fedterm-sp-http
+
+ true
+
+ """ % cfg
+
+ orga = ''
+ if cfg.get('organization_name'):
+ orga = """
+
+ %s
+ """ % unicode(cfg['organization_name'], 'iso-8859-1').encode('utf-8')
+
+ epilogue = """
+ """
+
+ return '\n'.join([prologue, sp_head, signing_public_key, sp_body, orga, epilogue])
+
+
+
+def get_saml2_metadata(cfg):
+ prologue = """\
+
+""" % cfg
+
+ sp_head = """
+ """
+
+ signing_public_key = ''
+ if cfg.has_key('signing_public_key') and cfg['signing_public_key']:
+ if 'CERTIF' in cfg['signing_public_key']:
+ signing_public_key = """
+
+
+ %s
+
+ """ % cfg['signing_public_key']
+ elif 'KEY' in cfg['signing_public_key']:
+ signing_public_key = """
+
+
+ %s
+
+ """ % cfg['signing_public_key']
+
+ sp_body = """
+
+
+
+
+ """ % cfg
+
+ orga = ''
+ if cfg.get('organization_name'):
+ orga = """
+
+ %s
+ """ % unicode(cfg['organization_name'], 'iso-8859-1').encode('utf-8')
+
+ epilogue = """
+ """
+
+ return '\n'.join([prologue, sp_head, signing_public_key, sp_body, orga, epilogue])
+
diff --git a/larpe/tags/release-1.1.1/larpe/admin/root.ptl b/larpe/tags/release-1.1.1/larpe/admin/root.ptl
new file mode 100644
index 0000000..8a65592
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/root.ptl
@@ -0,0 +1,90 @@
+import os
+
+import lasso
+
+from quixote import get_session, get_session_manager, get_publisher, get_request, get_response
+from quixote.directory import Directory, AccessControlled
+
+from qommon.admin.menu import html_top
+from qommon.admin import logger
+
+from larpe import errors
+from larpe import misc
+
+import hosts
+import users
+import settings
+
+def gpl [html] ():
+ """This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ Place - Suite 330, Boston, MA 02111-1307, USA.
+ """
+
+
+class RootDirectory(AccessControlled, Directory):
+ _q_exports = ['', 'hosts', 'users', 'settings', 'logger']
+
+ hosts = hosts.HostsDirectory()
+ users = users.UsersDirectory()
+ settings = settings.SettingsDirectory()
+ logger = logger.LoggerDirectory()
+
+ menu_items = [
+ ('hosts/', N_('Hosts')),
+ ('users/', N_('Users')),
+ ('settings/', N_('Settings')),
+ ('logger/', N_('Logs')),
+ ('/', N_('Liberty Alliance Reverse Proxy'))]
+
+ def _q_access(self):
+ # FIXME : this block should be moved somewhere else
+ get_publisher().reload_cfg()
+ if not get_publisher().cfg.has_key('proxy_hostname'):
+ get_publisher().cfg['proxy_hostname'] = get_request().get_server().split(':')[0]
+ get_publisher().write_cfg()
+
+ response = get_response()
+ if not hasattr(response, 'breadcrumb'):
+ response.breadcrumb = [ ('../admin/', _('Administration')) ]
+
+ # Cheater
+ if os.path.exists(os.path.join(get_publisher().app_dir, 'ADMIN_FOR_ALL')):
+ return
+
+ # No admin user created yet, free access
+ user_list = users.User.select(lambda x: x.is_admin)
+ if not user_list:
+ return
+
+ host_list = hosts.Host.select(lambda x: x.name == 'larpe')
+ if host_list:
+ host = host_list[0]
+ else:
+ raise errors.AccessForbiddenError()
+
+ if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
+ user = get_session().get_user(host.saml2_provider_id)
+ else:
+ user = get_session().get_user(host.provider_id)
+ if user:
+ if not user.name or not user.is_admin:
+ raise errors.AccessForbiddenError()
+ else:
+ raise errors.AccessUnauthorizedError()
+
+
+ def _q_index [html] (self):
+ html_top('')
+ gpl()
+
diff --git a/larpe/tags/release-1.1.1/larpe/admin/settings.ptl b/larpe/tags/release-1.1.1/larpe/admin/settings.ptl
new file mode 100644
index 0000000..a1a36f1
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/settings.ptl
@@ -0,0 +1,361 @@
+import cStringIO
+import cPickle
+import re
+import os
+import lasso
+import glob
+import zipfile
+
+from quixote import get_publisher, get_request, get_response, redirect
+from quixote.directory import Directory, AccessControlled
+
+from qommon.form import *
+from qommon.misc import get_abs_path
+from qommon.admin.cfg import cfg_submit
+from qommon.admin.menu import html_top, error_page
+from qommon.admin.emails import EmailsDirectory as QommonEmailsDirectory
+from qommon.admin.settings import SettingsDirectory as QommonSettingsDirectory
+
+from larpe import misc
+from larpe.hosts import Host
+from larpe.admin.liberty_utils import *
+
+class LibertyIDPDir(Directory):
+ _q_exports = ['', ('metadata.xml', 'metadata')]
+
+ def _q_index [html] (self):
+ form = Form(enctype="multipart/form-data")
+ form.add(FileWidget, "metadata", title = _("Metadata"), required=True)
+ form.add(FileWidget, "publickey", title = _("Public Key"), required=False)
+ form.add(FileWidget, "cacertchain", title = _("CA Certificate Chain"), required=False)
+ form.add_submit("submit", _("Submit"))
+
+ if not form.is_submitted() or form.has_errors():
+ html_top('settings', title = _('New Identity Provider'))
+ "%s " % _('New Identity Provider')
+ form.render()
+ else:
+ self.submit_new(form)
+
+ def submit_new(self, form, key_provider_id = None):
+ metadata, publickey, cacertchain = None, None, None
+ if form.get_widget('metadata').parse():
+ metadata = form.get_widget('metadata').parse().fp.read()
+ if form.get_widget('publickey').parse():
+ publickey = form.get_widget('publickey').parse().fp.read()
+ if form.get_widget('cacertchain').parse():
+ cacertchain = form.get_widget('cacertchain').parse().fp.read()
+
+ if not key_provider_id:
+ try:
+ provider_id = re.findall(r'(provider|entity)ID="(.*?)"', metadata)[0][1]
+ except IndexError:
+ return error_page('settings', _('Bad metadata'))
+ key_provider_id = provider_id.replace(str('://'), str('-')).replace(str('/'), str('-'))
+
+ dir = get_abs_path(os.path.join('idp', key_provider_id))
+ if not os.path.isdir(dir):
+ os.makedirs(dir)
+
+ if metadata:
+ metadata_fn = os.path.join(dir, 'metadata.xml')
+ open(metadata_fn, 'w').write(metadata)
+ if publickey:
+ publickey_fn = os.path.join(dir, 'public_key')
+ open(publickey_fn, 'w').write(publickey)
+ else:
+ publickey_fn = None
+ if cacertchain:
+ cacertchain_fn = os.path.join(dir, 'ca_cert_chain.pem')
+ open(cacertchain_fn, 'w').write(cacertchain)
+ else:
+ cacertchain_fn = None
+
+ p = lasso.Provider(lasso.PROVIDER_ROLE_IDP, metadata_fn, publickey_fn, None)
+
+ try:
+ misc.get_provider_label(p)
+ get_publisher().cfg['idp'] = key_provider_id
+ get_publisher().write_cfg()
+ except TypeError:
+ if metadata:
+ os.unlink(metadata_fn)
+ if publickey:
+ os.unlink(publickey_fn)
+ if cacertchain:
+ os.unlink(cacertchain_fn)
+ return error_page('settings', _('Bad metadata'))
+
+ redirect('..')
+
+ def metadata(self):
+ response = get_response()
+ response.set_content_type('text/xml', 'utf-8')
+ get_publisher().reload_cfg()
+ if get_publisher().cfg['idp']:
+ idp_metadata = os.path.join(get_abs_path('idp'), get_publisher().cfg['idp'], 'metadata.xml')
+ return unicode(open(idp_metadata).read(), 'utf-8')
+ return 'No IDP is configured'
+
+
+class EmailsDirectory(QommonEmailsDirectory):
+ def _q_index [html] (self):
+ # Don't use custom emails
+ html_top('settings', title = _('Emails'))
+ '%s ' % _('Emails')
+
+ ''
+ '%s ' % _('General Options')
+ ' '
+
+ ''
+ '%s ' % _('Back')
+ '
'
+
+
+class SettingsDirectory(QommonSettingsDirectory):
+ _q_exports = ['', 'liberty_sp', 'liberty_idp', 'domain_names', 'apache2_configuration_generation',
+ 'proxy', 'language', 'emails', 'debug_options' ]
+
+ liberty_idp = LibertyIDPDir()
+ emails = EmailsDirectory()
+
+ def _q_index [html] (self):
+ get_publisher().reload_cfg()
+ html_top('settings', title = _('Settings'))
+
+ if lasso.SAML2_SUPPORT:
+ '%s ' % _('Liberty Alliance & SAML 2.0 Service Provider')
+ else:
+ '%s ' % _('Liberty Alliance Service Provider')
+ ' %s %s ' % (
+ _('Service Provider'), _('Configure Larpe as a Service Provider'))
+
+ hosts = Host.select(lambda x: x.name == 'larpe')
+ if hosts:
+ self.host = hosts[0]
+
+ if lasso.SAML2_SUPPORT and self.host.saml2_metadata is not None:
+ metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
+ '%s %s ' % (
+ metadata_url,
+ _('SAML 2.0 Metadata'),
+ _('Download SAML 2.0 metadata file for Larpe'))
+
+ if self.host.metadata is not None:
+ metadata_url = '%s/metadata.xml' % self.host.base_url
+ '%s %s ' % (
+ metadata_url,
+ _('ID-FF 1.2 Metadata'),
+ _('Download ID-FF 1.2 metadata file for Larpe'))
+
+ if self.host.public_key is not None:
+ public_key_url = '%s/public_key' % self.host.base_url
+ '%s %s ' % (
+ public_key_url,
+ _('Public key'),
+ _('Download SSL Public Key file'))
+
+ if lasso.SAML2_SUPPORT:
+ '%s ' % _('Liberty Alliance & SAML 2.0 Identity Provider')
+ else:
+ '%s ' % _('Liberty Alliance Identity Provider')
+
+ ''
+
+ '%s %s ' % (
+ _('Identity Provider'), _('Configure an identity provider'))
+
+ if get_publisher().cfg.has_key('idp'):
+ '%s %s ' % (
+ _('Identity Provider metadatas'), _('See current identity provider metadatas'))
+
+ ' '
+
+ '%s ' % _('Global parameters for the sites')
+
+ ''
+ '%s %s ' % (
+ _('Domain name'), _('Configure the base domain name for the sites'))
+ '%s %s ' % (
+ _('Apache 2 configuration generation'), _('Customise Apache 2 configuration generation'))
+ '%s %s ' % (
+ _('Proxy'), _('Connect to the sites through a web proxy'))
+ ' '
+
+ '%s ' % _('Customisation')
+
+ ''
+ '%s %s ' % (
+ _('Language'), _('Configure site language'))
+ '%s %s ' % (
+ _('Emails'), _('Configure email settings'))
+ ' '
+
+ '%s ' % _('Misc')
+
+ ''
+ '%s %s ' % (
+ _('Debug Options'), _('Configure options useful for debugging'))
+ ' '
+
+
+ def liberty_sp [html] (self):
+ get_publisher().reload_cfg()
+
+ # Get the host object for the reverse proxy
+ hosts = Host.select(lambda x: x.name == 'larpe')
+ if hosts:
+ self.host = hosts[0]
+ else:
+ self.host = Host()
+ self.host.reversed_hostname = get_publisher().cfg[str('proxy_hostname')]
+
+ form = Form(enctype='multipart/form-data')
+ form.add(StringWidget, 'organization_name', title=_('Organisation Name'), size=50,
+ required = True, value = self.host.organization_name)
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ if form.get_widget('cancel').parse():
+ return redirect('.')
+ if not form.is_submitted() or form.has_errors():
+ html_top('settings', title = _('Service Provider Configuration'))
+ '%s ' % _('Service Provider Configuration')
+ form.render()
+ else:
+ self.liberty_sp_submit(form)
+ redirect('.')
+
+ def liberty_sp_submit(self, form):
+ get_publisher().reload_cfg()
+ metadata_cfg = {}
+
+ f = 'organization_name'
+ if form.get_widget(f):
+ setattr(self.host, f, form.get_widget(f).parse())
+
+ metadata_cfg['organization_name'] = self.host.organization_name
+
+ self.host.name = 'larpe'
+
+ # Liberty Alliance / SAML parameters
+ base_url = '%s/liberty/%s/liberty' % (misc.get_root_url(), self.host.name)
+ metadata_cfg['base_url'] = base_url
+ self.host.base_url = base_url
+
+ if lasso.SAML2_SUPPORT:
+ saml2_base_url = '%s/liberty/%s/saml' % (misc.get_root_url(), self.host.name)
+ metadata_cfg['saml2_base_url'] = saml2_base_url
+ self.host.saml2_base_url = saml2_base_url
+
+ provider_id = '%s/metadata' % base_url
+ metadata_cfg['provider_id'] = provider_id
+ self.host.provider_id = provider_id
+
+ if lasso.SAML2_SUPPORT:
+ saml2_provider_id = '%s/metadata' % saml2_base_url
+ metadata_cfg['saml2_provider_id'] = saml2_provider_id
+ self.host.saml2_provider_id = saml2_provider_id
+
+ # Storage directories
+ site_dir = os.path.join(get_publisher().app_dir, 'sp',
+ self.host.reversed_hostname, self.host.name)
+ user_dir = os.path.join(site_dir, 'users')
+ token_dir = os.path.join(site_dir, 'tokens')
+ for dir in (site_dir, user_dir, token_dir):
+ if not os.path.isdir(dir):
+ os.makedirs(dir)
+ metadata_cfg['site_dir'] = site_dir
+ self.host.site_dir = site_dir
+
+ # Generate SSL keys
+ private_key_path = os.path.join(site_dir, 'private_key.pem')
+ public_key_path = os.path.join(site_dir, 'public_key')
+ if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
+ set_provider_keys(private_key_path, public_key_path)
+ self.host.private_key = private_key_path
+ metadata_cfg['signing_public_key'] = open(public_key_path).read()
+ self.host.public_key = public_key_path
+
+ # Write metadatas
+ metadata_path = os.path.join(site_dir, 'metadata.xml')
+ open(metadata_path, 'w').write(get_metadata(metadata_cfg))
+ self.host.metadata = metadata_path
+
+ if hasattr(self.host, 'saml2_provider_id'):
+ saml2_metadata_path = os.path.join(site_dir, 'saml2_metadata.xml')
+ open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
+ self.host.saml2_metadata = saml2_metadata_path
+
+ self.host.root_url = '%s/' % misc.get_root_url()
+ self.host.return_url = '%s/admin/' % misc.get_root_url()
+
+ self.host.store()
+
+ def domain_names [html] (self):
+ form = self.form_domain_name()
+
+ if form.get_widget('cancel').parse():
+ return redirect('.')
+
+ if not form.is_submitted() or form.has_errors():
+ html_top('settings', title = _('Domain name'))
+ '%s ' % _('Domain name')
+ form.render()
+ else:
+ self.submit_domain_name(form)
+ redirect('.')
+
+ def form_domain_name(self):
+ get_publisher().reload_cfg()
+ if get_cfg('domain_names'):
+ domain_name = get_cfg('domain_names')[0]
+ else:
+ domain_name = None
+
+ form = Form(enctype='multipart/form-data')
+ form.add(StringWidget, 'domain_name',
+ title=_('Domain name for the sites'),
+ value = domain_name)
+ # TODO: Add the option "Both" and handle it in hosts configuration
+ form.add(SingleSelectWidget, 'sites_url_scheme', title = _('Use HTTP or HTTPS'),
+ value = get_cfg('sites_url_scheme'),
+ options = [ (None, _('Same as the site')),
+ ('http', 'HTTP'),
+ ('https', 'HTTPS') ] )
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ return form
+
+ def submit_domain_name(self, form):
+ get_publisher().reload_cfg()
+ get_publisher().cfg['domain_names'] = [ form.get_widget('domain_name').parse() ]
+ get_publisher().cfg['sites_url_scheme'] = form.get_widget('sites_url_scheme').parse()
+ get_publisher().write_cfg()
+
+ def apache2_configuration_generation [html] (self):
+ get_publisher().reload_cfg()
+
+ form = Form(enctype='multipart/form-data')
+ form.add(CheckboxWidget, 'allow_config_generation',
+ title=_('Automatically generate Apache 2 configuration for new hosts and reload Apache 2 after changes'),
+ value = get_publisher().cfg.get(str('allow_config_generation'), True))
+ form.add_submit('submit', _('Submit'))
+ form.add_submit('cancel', _('Cancel'))
+ if form.get_widget('cancel').parse():
+ return redirect('.')
+ if not form.is_submitted() or form.has_errors():
+ html_top('settings', title = _('Apache 2 configuration generation'))
+ '%s ' % _('Apache 2 configuration generation')
+ form.render()
+ else:
+ self.apache2_configuration_generation_submit(form)
+ redirect('.')
+
+ def apache2_configuration_generation_submit(self, form):
+ get_publisher().reload_cfg()
+
+ f = 'allow_config_generation'
+ get_publisher().cfg[f] = form.get_widget(f).parse()
+
+ get_publisher().write_cfg()
diff --git a/larpe/tags/release-1.1.1/larpe/admin/users.ptl b/larpe/tags/release-1.1.1/larpe/admin/users.ptl
new file mode 100644
index 0000000..a0aee01
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/admin/users.ptl
@@ -0,0 +1,276 @@
+import random
+
+import lasso
+
+from quixote import get_request, get_session, redirect, get_publisher
+from quixote.directory import Directory
+
+from qommon.admin.menu import html_top, error_page, command_icon
+from qommon.errors import EmailError
+from qommon.form import *
+from qommon import emails
+
+from larpe import errors
+from larpe import misc
+from larpe.users import User
+from larpe.hosts import Host
+
+class UserUI:
+ def __init__(self, user):
+ self.user = user
+
+ def form_new(self):
+ form = Form(enctype="multipart/form-data")
+ form.add(StringWidget, "name", title = _('User Name'), required = True, size=30)
+ form.add(StringWidget, "email", title = _('Email'), required = False, size=30)
+ form.add_submit("submit", _("Submit"))
+ form.add_submit("cancel", _("Cancel"))
+ return form
+
+ def form_edit(self):
+ form = Form(enctype="multipart/form-data")
+ form.add(StringWidget, "name", title = _('User Name'), required = True, size=30,
+ value = self.user.name)
+ form.add(StringWidget, "email", title = _('Email'), required = False, size=30,
+ value = self.user.email)
+ form.add_submit("submit", _("Submit"))
+ form.add_submit("cancel", _("Cancel"))
+ return form
+
+ def submit_form(self, form):
+ if not self.user:
+ self.user = User()
+ for f in ('name', 'email'):
+ widget = form.get_widget(f)
+ if widget:
+ setattr(self.user, f, widget.parse())
+ self.user.is_admin = True
+ self.user.store()
+
+
+class UserPage(Directory):
+ _q_exports = ['', 'edit', 'delete', 'token']
+
+ def __init__(self, component):
+ self.user = User.get(component)
+ self.user_ui = UserUI(self.user)
+ get_response().breadcrumb.append((component + '/', self.user.name))
+
+ def _q_index [html] (self):
+ html_top('users', '%s - %s' % (_('User'), self.user.name))
+ '%s - %s ' % (_('User'), self.user.name)
+ ''
+
+ def debug [html] (self):
+ get_response().breadcrumb.append( ('debug', _('Debug')) )
+ html_top('users', 'Debug')
+ "Debug - %s " % self.user.name
+ ""
+ self.user.lasso_dump
+ " "
+
+ def edit [html] (self):
+ form = self.user_ui.form_edit()
+ if form.get_widget('cancel').parse():
+ return redirect('..')
+ if not form.is_submitted() or form.has_errors():
+ get_response().breadcrumb.append( ('edit', _('Edit')) )
+ html_top('users', title = _('Edit User'))
+ '%s ' % _('Edit User')
+ form.render()
+ else:
+ self.user_ui.submit_form(form)
+ return redirect('..')
+
+ def delete [html] (self):
+ form = Form(enctype="multipart/form-data")
+ form.widgets.append(HtmlWidget('%s
' % _(
+ "You are about to irrevocably delete this user.")))
+ form.add_submit("submit", _("Submit"))
+ form.add_submit("cancel", _("Cancel"))
+ if form.get_widget('cancel').parse():
+ return redirect('..')
+ if not form.is_submitted() or form.has_errors():
+ get_response().breadcrumb.append(('delete', _('Delete')))
+ html_top('users', title = _('Delete User'))
+ '%s %s ' % (_('Deleting User :'), self.user.name)
+ form.render()
+ else:
+ self.user.remove_self()
+ return redirect('..')
+
+ def token [html] (self):
+ form = Form(enctype="multipart/form-data", use_tokens = False)
+ form.add_submit("submit", _("Generate"))
+ form.add_submit("cancel", _("Cancel"))
+ request = get_request()
+ if request.form.has_key('cancel') or request.form.has_key('done'):
+ return redirect('..')
+
+ get_response().breadcrumb.append(('token', _('Identification Token')))
+
+ if not form.is_submitted() or form.has_errors():
+ html_top('users', title = _('Identification Token'))
+ '%s ' % _('Identification Token')
+ '%s
' % _('You are about to generate a token than can be used to federate the account.')
+ '%s
' % _('After that, you will have the choice to send it to the user by email so that he can federate his accounts.')
+ if self.user.identification_token:
+ '%s
' % _('Note that user has already been issued an identification token : %s') % self.user.identification_token
+ form.render()
+ else:
+ if request.form.has_key('submit'):
+ html_top('users', title = _('Identification Token'))
+ token = '-'.join(['%04d' % random.randint(1, 9999) for x in range(4)])
+ self.user.identification_token = str(token)
+ self.user.store()
+
+ ''
+ _('Identification Token for %s') % self.user.name
+ ' : %s
' % self.user.identification_token
+
+ form = Form(enctype="multipart/form-data", use_tokens = False)
+ form.add_submit('done', _('Done'))
+ if self.user.email:
+ form.add_submit("submit-email", _("Send by email"))
+ form.render()
+ else:
+ site_url = '%s://%s%s/token?token=%s' \
+ % (request.get_scheme(), request.get_server(),
+ get_request().environ['SCRIPT_NAME'], self.user.identification_token)
+ body = _("""You have been given an identification token.
+
+Your token is %(token)s
+
+Click on %(url)s to use it.
+""") % {'token': self.user.identification_token, 'url': site_url}
+ try:
+ emails.email(_('Identification Token'), body, self.user.email)
+ except EmailError, e:
+ html_top('users', title = _('Identification Token'))
+ _('Failed sending email. Check your email configuration.')
+ ' ' % _('Back')
+ else:
+ return redirect('..')
+
+class UsersDirectory(Directory):
+
+ _q_exports = ['', 'new']
+
+ def _q_index [html] (self):
+ get_publisher().reload_cfg()
+ get_response().breadcrumb.append( ('users/', _('Users')) )
+ html_top('users', title = _('Users'))
+
+
+ if not list(Host.select(lambda x: x.name == 'larpe')):
+ '%s
' % _('Liberty support must be setup before creating users.')
+ else:
+ """""" % _('New User')
+
+ debug_cfg = get_publisher().cfg.get('debug', {})
+
+ users = User.select(lambda x: x.name is not None, order_by = 'name')
+
+ ''
+ for user in users:
+ ''
+ '%s ' % user.name
+ if user.email:
+ ''
+ user.email
+ '
'
+
+ ''
+ command_icon('%s/' % user.id, 'view')
+ if not user.name_identifiers:
+ if not user.identification_token:
+ command_icon('%s/token' % user.id, 'token',
+ label = _('Identification Token'), icon = 'stock_exec_16.png')
+ else:
+ command_icon('%s/token' % user.id, 'token',
+ label = _('Identification Token (current: %s)') % \
+ user.identification_token,
+ icon = 'stock_exec_16.png')
+ command_icon('%s/edit' % user.id, 'edit')
+ command_icon('%s/delete' % user.id, 'remove')
+ if debug_cfg.get('logger', False):
+ command_icon('../logger/by_user/%s/' % user.id, 'logs',
+ label = _('Logs'), icon = 'stock_harddisk_16.png')
+ '
'
+ ' '
+
+ def new [html] (self):
+ get_response().breadcrumb.append( ('users/', _('Users')) )
+ get_response().breadcrumb.append( ('new', _('New')) )
+ hosts = list(Host.select(lambda x: x.name == 'larpe'))
+ if not hosts:
+ return error_page('users', _('Liberty support must be setup before creating users.'))
+ host = hosts[0]
+ # XXX: user must be logged in to get here
+ user_ui = UserUI(None)
+ # FIXME : should be able to use User.count(). Track fake user creations.
+ users = User.select(lambda x: x.name is not None)
+ first_user = (len(users) == 0)
+ form = user_ui.form_new()
+ if form.get_widget('cancel').parse():
+ return redirect('.')
+
+ if not form.is_submitted() or form.has_errors():
+ html_top('users', title = _('New User'))
+ '%s ' % _('New User')
+ form.render()
+ else:
+ user_ui.submit_form(form)
+ if first_user:
+ session = get_session()
+ if hasattr(session, str('lasso_dump')):
+ user_ui.user.name_identifiers = [ session.name_identifier ]
+ user_ui.user.lasso_dumps = [ session.lasso_anonymous_identity_dump ]
+ user_ui.user.store()
+ if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
+ get_session().set_user(user_ui.user.id, host.saml2_provider_id)
+ else:
+ get_session().set_user(user_ui.user.id, host.provider_id)
+ return redirect('.')
+
+ def _q_lookup(self, component):
+ get_response().breadcrumb.append( ('users/', _('Users')) )
+ try:
+ return UserPage(component)
+ except KeyError:
+ raise errors.TraversalError()
diff --git a/larpe/tags/release-1.1.1/larpe/ctl/__init__.py b/larpe/tags/release-1.1.1/larpe/ctl/__init__.py
new file mode 100644
index 0000000..35a5923
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/ctl/__init__.py
@@ -0,0 +1,2 @@
+from start import start
+
diff --git a/larpe/tags/release-1.1.1/larpe/ctl/start.py b/larpe/tags/release-1.1.1/larpe/ctl/start.py
new file mode 100644
index 0000000..c404202
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/ctl/start.py
@@ -0,0 +1,52 @@
+import socket
+import sys
+import quixote.server.simple_server
+
+from qommon.scgi_server import run
+
+import publisher
+
+def start(args):
+ run_function = run
+ run_kwargs = {
+ 'port': 3007,
+ 'script_name': ''
+ }
+ http = 0
+
+ i = 0
+ while i < len(args):
+ if args[i] == '--port':
+ run_kwargs['port'] = int(args[i+1])
+ i += 1
+ elif args[i] == '--silent':
+ sys.stdout = open('/dev/null', 'w')
+ sys.stderr = open('/dev/null', 'w')
+ elif args[i] == '--script-name':
+ run_kwargs['script_name'] = args[i+1]
+ i += 1
+ elif args[i] == '--app-dir':
+ publisher.LarpePublisher.APP_DIR = args[i+1]
+ i += 1
+ elif args[i] == '--data-dir':
+ publisher.LarpePublisher.DATA_DIR = args[i+1]
+ i += 1
+ elif args[i] == '--http':
+ http = 1
+ i += 1
+
+ if http == 1:
+ run_function = quixote.server.simple_server.run
+ if run_kwargs['script_name']:
+ print "--http option is incompatible with --script-name"
+ del run_kwargs['script_name']
+ try:
+ run_function(publisher.LarpePublisher.create_publisher, **run_kwargs)
+ except socket.error, err:
+ if err[0] == 98:
+ print >> sys.stderr, 'address already in use'
+ sys.exit(1)
+ raise
+ except KeyboardInterrupt:
+ sys.exit(1)
+
diff --git a/larpe/tags/release-1.1.1/larpe/errors.ptl b/larpe/tags/release-1.1.1/larpe/errors.ptl
new file mode 100644
index 0000000..c1f0274
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/errors.ptl
@@ -0,0 +1,15 @@
+from quixote import get_session, get_request, redirect
+
+from qommon.errors import *
+
+class AccessUnauthorizedError(AccessError):
+ def render [html] (self):
+ session = get_session()
+ request = get_request()
+ query = request.get_query()
+ session.after_url = request.get_url()
+ if query:
+ session.after_url += '?' + query
+ session.after_url = str(session.after_url)
+ login_url = '%s/liberty/larpe/login' % request.environ['SCRIPT_NAME']
+ redirect(login_url)
diff --git a/larpe/tags/release-1.1.1/larpe/federations.py b/larpe/tags/release-1.1.1/larpe/federations.py
new file mode 100644
index 0000000..e76328f
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/federations.py
@@ -0,0 +1,35 @@
+'''Federation object. Configuration variables and utilities'''
+
+from qommon.storage import StorableObject
+
+class Federation(StorableObject):
+ _names = 'federations'
+
+ username = None
+ password = None
+ host_id = None
+ name_identifiers = None
+ cookies = None
+ select_fields = {}
+
+ def __init__(self, username, password, host_id, name_identifier, cookies=None, select=None):
+ select = select or {}
+ StorableObject.__init__(self)
+ self.username = username
+ self.password = password
+ self.host_id = host_id
+ self.name_identifiers = [ name_identifier ]
+ self.cookies = cookies
+ self.select_fields = select
+
+ def remove_name_identifier(self, name_identifier):
+ self.name_identifiers.remove(name_identifier)
+ if not self.name_identifiers:
+ self.remove_self()
+
+ def set_cookies(self, cookies):
+ self.cookies = cookies
+
+ def __str__(self):
+ return 'Federation username : %s, name identifiers : %s, cookies : %s' \
+ % (self.username, self.name_identifiers, self.cookies)
diff --git a/larpe/tags/release-1.1.1/larpe/field_prefill.py b/larpe/tags/release-1.1.1/larpe/field_prefill.py
new file mode 100644
index 0000000..389d887
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/field_prefill.py
@@ -0,0 +1,13 @@
+from qommon.storage import StorableObject
+
+class FieldPrefill(StorableObject):
+ _names = 'field_prefill'
+
+ form_id = 0
+ name = None
+ xpath = None
+ number = 1
+ raw_xml = False
+ regexp_match = None
+ regexp_replacing = None
+ select_options = {}
diff --git a/larpe/tags/release-1.1.1/larpe/form_prefill.py b/larpe/tags/release-1.1.1/larpe/form_prefill.py
new file mode 100644
index 0000000..2c4727a
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/form_prefill.py
@@ -0,0 +1,10 @@
+from qommon.storage import StorableObject
+
+class FormPrefill(StorableObject):
+ _names = 'form_prefill'
+
+ host_id = 0
+ name = None
+ url = None
+ profile = None
+ prefix = None
diff --git a/larpe/tags/release-1.1.1/larpe/hosts.py b/larpe/tags/release-1.1.1/larpe/hosts.py
new file mode 100644
index 0000000..9b48ebb
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/hosts.py
@@ -0,0 +1,136 @@
+'''Host object. Configuration variables and utilities'''
+
+import os
+from shutil import rmtree
+
+from quixote import get_request
+
+from qommon.storage import StorableObject
+
+from Defaults import APP_DIR
+
+def get_proxied_site_name():
+ nb_subdirs = get_request().environ['SCRIPT_NAME'].count('/')
+ return get_request().get_path().split('/')[nb_subdirs + 2]
+
+class Host(StorableObject):
+ '''Host object. Configuration variables and utilities'''
+ _names = 'hosts'
+
+ # Main settings
+ label = None
+ name = None
+ orig_site = None
+ new_url = None
+ scheme = None
+ auth_url = None
+ auth_form_places = 'form_once'
+ auth_form_page_url = None
+ auth_form = None
+ auth_form_url = None
+ logout_url = None
+ reversed_hostname = None
+ reversed_directory = None
+ organization_name = None
+ use_ssl = False
+ private_key = None
+ public_key = None
+ site_dir = None
+
+ # Auto detected settings
+ auth_mode = 'form'
+ auth_form_action = None
+ auth_check_url = None
+ login_field_name = None
+ password_field_name = None
+ select_fields = {}
+ post_parameters = {}
+ http_headers = {}
+
+ # Advanced settings
+ return_url = '/'
+ root_url = '/'
+ auth_system = 'password'
+ auth_match_text = ''
+ send_hidden_fields = True
+ initiate_sso_url = None
+ redirect_root_to_login = False
+
+ # Other attributes
+ provider_id = None
+ # Default value that indicates the proxy (if configured) is not disabled for this host yet
+ use_proxy = True
+
+ valid_username = None
+ valid_password = None
+ apache_output_filters = []
+ apache_output_python_filters = []
+ apache_python_paths = []
+
+ # Plugins
+ # If name is set to None, use the default site authentication class
+ site_authentication_plugin = None
+
+ def get_host_from_url(cls):
+ try:
+ host = list(Host.select(lambda x: x.name == get_proxied_site_name()))[0]
+ if hasattr(host, 'site_authentication_instance'):
+ del host.site_authentication_instance
+ return list(Host.select(lambda x: x.name == get_proxied_site_name()))[0]
+ except IndexError:
+ return None
+ get_host_from_url = classmethod(get_host_from_url)
+
+ def get_host_with_provider_id(cls, provider_id):
+ try:
+ return list(Host.select(lambda x: x.provider_id == provider_id))[0]
+ except IndexError:
+ return None
+ get_host_with_provider_id = classmethod(get_host_with_provider_id)
+
+ def get_root_url(self):
+ if self.root_url.startswith('/'):
+ if self.reversed_directory:
+ return '%s/%s%s' % (get_request().environ['SCRIPT_NAME'],
+ self.reversed_directory,
+ self.root_url)
+ else:
+ return '%s%s' % (get_request().environ['SCRIPT_NAME'], self.root_url)
+ # In this case, must be a full url
+ return self.root_url
+
+ def get_return_url(self):
+ if self.return_url.startswith('/'):
+ if self.reversed_directory:
+ return '%s/%s%s' % (get_request().environ['SCRIPT_NAME'],
+ self.reversed_directory,
+ self.return_url)
+ else:
+ return '%s%s' % (get_request().environ['SCRIPT_NAME'], self.return_url)
+ # In this case, must be a full url
+ return self.return_url
+
+ def __cmp__(self, other):
+ hostname_cmp = cmp(self.reversed_hostname, other.reversed_hostname)
+ if hostname_cmp != 0:
+ return hostname_cmp
+ return cmp(self.reversed_directory, other.reversed_directory)
+
+ def remove_self(self):
+ # Main configuration file
+ StorableObject.remove_self(self)
+ # Other generated files
+ if self.site_dir and os.path.exists(self.site_dir):
+ rmtree(self.site_dir, ignore_errors=1)
+ # Also remove hostname directory if empty (meaning there was no other subdirectory
+ # for this hostname)
+ try:
+ os.rmdir('/'.join(self.site_dir.split('/')[:-1]))
+ except OSError:
+ pass
+ # Virtual host directory
+ if self.reversed_hostname:
+ path = os.path.join(APP_DIR, self.reversed_hostname)
+ if os.path.exists(path):
+ rmtree(path, ignore_errors=1)
+
diff --git a/larpe/tags/release-1.1.1/larpe/idwsf2.ptl b/larpe/tags/release-1.1.1/larpe/idwsf2.ptl
new file mode 100644
index 0000000..fbf807f
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/idwsf2.ptl
@@ -0,0 +1,199 @@
+import os
+import sys
+import re
+
+try:
+ import lasso
+except ImportError:
+ print >> sys.stderr, 'Missing Lasso module, IdWsf 2.0 support disabled'
+else:
+ if not lasso.WSF_SUPPORT:
+ print >> sys.stderr, 'Found Lasso module, but IdWsf 2.0 support not enabled'
+
+from quixote import get_publisher, get_session, get_request, get_response, redirect
+from quixote.directory import Directory
+
+from qommon.liberty import SOAPException, soap_call
+from qommon import template
+from qommon.misc import http_get_page
+
+import misc
+from form_prefill import FormPrefill
+from field_prefill import FieldPrefill
+
+def cleanup_html_value(value):
+ # Ensure the field value can be properly integrated in HTML code
+ value = value.replace('"', "'")
+ # Conversion to iso-8859-1
+ try:
+ value = unicode(value, 'utf-8').encode('iso-8859-1')
+ except UnicodeEncodeError:
+ return None
+
+class IdWsf2(Directory):
+ _q_exports = []
+
+ def _q_lookup(self, component):
+ if not hasattr(get_session(), 'prefill_form'):
+ get_session().prefill_form = component
+ get_session().after_url = get_request().get_url()
+ if get_request().get_query():
+ get_session().after_url += '?' + get_request().get_query()
+ return redirect('../saml/login')
+ else:
+ prefill_form = FormPrefill.get(get_session().prefill_form)
+ del get_session().prefill_form
+ if prefill_form:
+ try:
+ response, status, page, auth_header = http_get_page(prefill_form.url)
+ except:
+ return template.error_page(_('Failed connecting to the original site.'))
+ try:
+ fields = self.do_prefill_form(prefill_form)
+ if not fields:
+ raise lasso.Error
+ for key, value in get_request().get_fields().iteritems():
+ value = cleanup_html_value(value)
+ if value:
+ fields[key] = value
+ except lasso.Error:
+ return page + '' % \
+ _('Failed getting attributes from the attribute provider.')
+ except:
+ return page + '' % \
+ _('Failed getting attributes for an unknown reason.')
+
+ return self.send_prefilled_form(prefill_form, page, fields)
+
+ def do_prefill_form(self, prefill_form):
+ server = misc.get_lasso_server(protocol = 'saml2')
+ disco = lasso.IdWsf2Discovery(server)
+ if not get_session().lasso_session_dumps or not get_session().lasso_session_dumps[server.providerId]:
+ return None
+ disco.setSessionFromDump(get_session().lasso_session_dumps[server.providerId])
+
+ disco.initQuery()
+ disco.addRequestedServiceType(prefill_form.profile)
+ disco.buildRequestMsg()
+
+ try:
+ soap_answer = soap_call(disco.msgUrl, disco.msgBody)
+ except SOAPException:
+ return None
+ disco.processQueryResponseMsg(soap_answer)
+
+ service = disco.getService()
+ lasso.registerIdWsf2DstService(prefill_form.prefix, prefill_form.profile)
+
+ service.initQuery()
+
+ fields = FieldPrefill.select(lambda x: x.form_id == prefill_form.id)
+ for field in fields:
+ if field.xpath and field.name:
+ service.addQueryItem(field.xpath, field.name)
+
+ service.buildRequestMsg()
+
+ try:
+ soap_answer = soap_call(service.msgUrl, service.msgBody)
+ except SOAPException:
+ return None
+ service.processQueryResponseMsg(soap_answer)
+
+ fields_dict = {}
+ for field in fields:
+ if not field.xpath or not field.name:
+ continue
+ if field.number > 0:
+ number = field.number -1
+ try:
+ if field.raw_xml:
+ value = service.getAttributeNodes(field.name)[number]
+ else:
+ value = service.getAttributeStrings(field.name)[number]
+ except (IndexError, TypeError):
+ value = ''
+ # Log
+ if value:
+ # Regexp transformation
+ if field.regexp_match:
+ value = re.sub(field.regexp_match, field.regexp_replacing, value)
+ value = cleanup_html_value(value)
+ # Conversion of select field options
+ if field.select_options:
+ try:
+ value = field.select_options[value]
+ except (IndexError, KeyError):
+ pass
+ if not value:
+ continue
+ fields_dict[field.name] = value
+
+ return fields_dict
+
+ def send_prefilled_form(self, prefill_form, page, fields):
+ for field_name, new_value in fields.iteritems():
+ # Input
+ regex = re.compile('(.*)( ]*? id="%s".*?>)(.*)' % field_name,
+ re.DOTALL | re.IGNORECASE)
+ match = regex.match(page)
+ if not match:
+ regex = re.compile('(.*)( ]*? name="%s".*?>)(.*)' % field_name,
+ re.DOTALL | re.IGNORECASE)
+ match = regex.match(page)
+ if match:
+ before, input_field, after = match.groups()
+ if 'value="' in input_field.lower():
+ regex_sub = re.compile('value=".*?"', re.DOTALL | re.IGNORECASE)
+ input_field = regex_sub.sub('value="%s"' % new_value, input_field)
+ else:
+ input_field = input_field.replace(' ]*? id="%s".*?>)[^<]*(.*)' % field_name,
+ re.DOTALL | re.IGNORECASE)
+ match = regex.match(page)
+ if not match:
+ regex = re.compile('(.*.*)' % field_name,
+ re.DOTALL | re.IGNORECASE)
+ match = regex.match(page)
+ if match:
+ before, after = match.groups()
+ page = ''.join([before, new_value, after])
+ continue
+
+ # Select
+ regex = re.compile('(.*]*? id="%s".*?>)(.*?)( .*)' % field_name,
+ re.DOTALL | re.IGNORECASE)
+ match = regex.match(page)
+ if not match:
+ regex = re.compile('(.*]*? name="%s".*?>)(.*?)( .*)' % field_name,
+ re.DOTALL | re.IGNORECASE)
+ match = regex.match(page)
+ if match:
+ before, options, after = match.groups()
+ # If the option to select is found, first unselect the previoulsy selected one
+ regex2 = re.compile('(.*]*? value="%s".*?)(>[^<]* .*)' % new_value,
+ re.DOTALL | re.IGNORECASE)
+ match2 = regex2.match(options)
+ if match2:
+ before2, after2 = match2.groups()
+ regex3 = re.compile('(.*]*?)( selected(="selected")?)(.*?>[^<]* .*)',
+ re.DOTALL | re.IGNORECASE)
+ match3 = regex3.match(options)
+ if match3:
+ before3, selected, selected_value, after3 = match3.groups()
+ options = ''.join([before3, after3])
+ regex2 = re.compile('(.*]*? value="%s".*?)(>[^<]* .*)' % new_value,
+ re.DOTALL | re.IGNORECASE)
+ match2 = regex2.match(options)
+ if match2:
+ before2, after2 = match2.groups()
+ options = ''.join([before2, ' selected="selected"', after2])
+
+ page = ''.join([before, options, after])
+
+ return page
+
diff --git a/larpe/tags/release-1.1.1/larpe/liberty.ptl b/larpe/tags/release-1.1.1/larpe/liberty.ptl
new file mode 100644
index 0000000..193dd19
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/liberty.ptl
@@ -0,0 +1,372 @@
+import libxml2
+import urllib
+import urlparse
+import httplib
+import re
+import os
+
+from quixote import get_field, get_request, get_response, get_session, get_session_manager, redirect
+from quixote.directory import Directory
+from quixote.http_request import parse_header
+
+import lasso
+
+from qommon import get_logger
+from qommon.form import *
+from qommon.template import *
+from qommon.liberty import soap_call, SOAPException
+
+import misc
+from users import User
+from hosts import Host
+from federations import Federation
+import site_authentication
+
+class Liberty(Directory):
+ _q_exports = ['', 'login', 'assertionConsumer', 'soapEndpoint',
+ 'singleLogout', 'singleLogoutReturn',
+ 'federationTermination', 'federationTerminationReturn',
+ ('metadata.xml', 'metadata'), 'public_key', 'local_auth']
+
+ def perform_login(self, idp = None):
+ server = misc.get_lasso_server()
+ login = lasso.Login(server)
+ login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
+ login.request.nameIdPolicy = 'federated'
+ login.request.forceAuthn = False
+ login.request.isPassive = False
+ login.request.consent = 'urn:liberty:consent:obtained'
+ login.buildAuthnRequestMsg()
+ return redirect(login.msgUrl)
+
+ def assertionConsumer(self):
+ server = misc.get_lasso_server()
+ if not server:
+ return error_page(_('Liberty support is not yet configured'))
+ login = lasso.Login(server)
+ request = get_request()
+ if request.get_method() == 'GET' or get_field('LAREQ'):
+ if request.get_method() == 'GET':
+ login.initRequest(request.get_query(), lasso.HTTP_METHOD_REDIRECT)
+ else:
+ login.initRequest(get_field('LAREQ'), lasso.HTTP_METHOD_POST)
+
+ login.buildRequestMsg()
+ try:
+ soap_answer = soap_call(login.msgUrl, login.msgBody)
+ except SOAPException:
+ return error_page(_('Failure to communicate with identity provider'))
+ try:
+ login.processResponseMsg(soap_answer)
+ except lasso.Error, error:
+ if error[0] == lasso.LOGIN_ERROR_STATUS_NOT_SUCCESS:
+ return error_page(_('Unknown authentication failure'))
+ if hasattr(lasso, 'LOGIN_ERROR_UNKNOWN_PRINCIPAL'):
+ if error[0] == lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
+ return error_page(_('Authentication failure; unknown principal'))
+ return error_page(_("Identity Provider didn't accept artifact transaction."))
+ else:
+ login.processAuthnResponseMsg(get_field('LARES'))
+ login.acceptSso()
+ session = get_session()
+ if login.isSessionDirty:
+ if login.session:
+ session.lasso_session_dumps[server.providerId] = login.session.dump()
+ else:
+ session.lasso_session_dumps[server.providerId] = None
+
+ # Look for an existing user
+ user = self.lookup_user(session, login)
+
+ # Check if it is for Larpe administration or token
+ host = Host.get_host_from_url()
+ if host is None:
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+ if host.name == 'larpe':
+ if user:
+ session.set_user(user.id, server.providerId)
+ else:
+ session.name_identifier = login.nameIdentifier.content
+ session.lasso_anonymous_identity_dump = login.identity.dump()
+ session.provider_id = server.providerId
+
+ if session.after_url:
+ # Access to an admin page or token url with parameter
+ after_url = session.after_url
+ session.after_url = None
+ return redirect(after_url)
+
+ if user and user.is_admin:
+ return redirect('%s/admin/' % get_request().environ['SCRIPT_NAME'])
+ else:
+ return redirect('%s/token' % get_request().environ['SCRIPT_NAME'])
+
+ # Set session user
+ if not user:
+ user = User()
+ user.name_identifiers = [ login.nameIdentifier.content ]
+ user.lasso_dumps = [ login.identity.dump() ]
+ user.store()
+ session.set_user(user.id, server.providerId)
+
+ federations = Federation.select(lambda x: host.id == x.host_id \
+ and user.name_identifiers[0] in x.name_identifiers)
+
+ if federations:
+ return site_authentication.get_site_authentication(host).sso_local_login(federations[0])
+ else:
+ response = get_response()
+ if session.after_url:
+ after_url = session.after_url
+ session.after_url = None
+ return redirect(after_url)
+ response.set_status(303)
+ response.headers['location'] = urlparse.urljoin(request.get_url(), str('local_auth'))
+ response.content_type = 'text/plain'
+ return 'Your browser should redirect you'
+
+ def lookup_user(self, session, login):
+ found_users = list(User.select(lambda x: login.nameIdentifier.content in x.name_identifiers))
+ if found_users:
+ return found_users[0]
+ return None
+
+ def singleLogout(self):
+ request = get_request()
+ logout = lasso.Logout(misc.get_lasso_server())
+ if lasso.isLibertyQuery(request.get_query()):
+ try:
+ logout.processRequestMsg(request.get_query())
+ except lasso.Error, error:
+ if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
+ return error_page(_('Failed to check single logout request signature.'))
+ raise
+ session = get_session()
+ if not session.id:
+ # session has not been found, this may be because the user has
+ # its browser configured so that cookies are not sent for
+ # remote queries and IdP is using image-based SLO.
+ # so we look up a session with the appropriate name identifier
+ for session in get_session_manager().values():
+ # This block differs from qommon
+ user = session.get_user(logout.server.providerId)
+ if user and logout.nameIdentifier.content in user.name_identifiers:
+ break
+ else:
+ session = get_session()
+ return self.slo_idp(logout, session)
+ else:
+ return self.slo_sp(logout, get_session())
+
+ def singleLogoutReturn(self):
+ logout = lasso.Logout(misc.get_lasso_server())
+ host = Host.get_host_from_url()
+ if host is None:
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+
+ try:
+ logout.processResponseMsg(get_request().get_query())
+ except lasso.Error, error:
+ if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
+ raise AccessError()
+ if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
+ return error_page(_('Failed to check single logout request signature.'))
+ if hasattr(lasso, 'LOGOUT_ERROR_REQUEST_DENIED') and \
+ error[0] == lasso.LOGOUT_ERROR_REQUEST_DENIED:
+ return redirect(host.get_root_url()) # ignore silently
+ elif error[0] == lasso.ERROR_UNDEFINED:
+ # XXX: unknown status; ignoring for now.
+ return redirect(host.get_root_url()) # ignore silently
+ raise
+ return redirect(host.get_root_url())
+
+ def slo_idp(self, logout, session):
+ '''Single Logout initiated by IdP'''
+ # This block differs from qommon
+ if session.lasso_session_dumps.has_key(logout.server.providerId):
+ logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
+ user = session.get_user(logout.server.providerId)
+ if user and user.lasso_dumps:
+ logout.setIdentityFromDump(user.lasso_dumps[0])
+ if user and logout.nameIdentifier.content not in user.name_identifiers:
+ raise 'No appropriate name identifier in user (%s and %s)' % (
+ logout.nameIdentifier.content, user.name_identifiers)
+
+ host = Host.get_host_with_provider_id(logout.server.providerId)
+ if host is not None:
+ site_authentication.get_site_authentication(host).local_logout(user=user)
+
+ try:
+ logout.validateRequest()
+ except lasso.Error, error:
+ if error[0] != lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
+ raise
+ else:
+ get_session_manager().expire_session(logout.server.providerId)
+
+ logout.buildResponseMsg()
+ if logout.msgBody: # soap answer
+ return logout.msgBody
+ else:
+ return redirect(logout.msgUrl)
+
+ def slo_sp(self, logout, session):
+ host = Host.get_host_from_url()
+ if host is None:
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+
+ if not session.id or not session.users.has_key(logout.server.providerId) \
+ or not session.lasso_session_dumps.has_key(logout.server.providerId):
+ get_session_manager().expire_session(logout.server.providerId)
+ return redirect(host.get_root_url())
+
+ logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
+ user = session.get_user(logout.server.providerId)
+
+ if host.name != 'larpe' and user:
+ site_authentication.get_site_authentication(host).local_logout(user=user)
+
+ if user and user.lasso_dumps:
+ logout.setIdentityFromDump(user.lasso_dumps[0])
+ else:
+ get_session_manager().expire_session(logout.server.providerId)
+ return redirect(host.get_root_url())
+
+ return self.slo_sp_redirect(logout, host)
+
+ def slo_sp_redirect(self, logout, host):
+ try:
+ logout.initRequest(None, lasso.HTTP_METHOD_REDIRECT)
+ except lasso.Error, error:
+ if error[0] == lasso.PROFILE_ERROR_NAME_IDENTIFIER_NOT_FOUND:
+ get_session_manager().expire_session()
+ return redirect(host.get_root_url())
+ raise
+ logout.buildRequestMsg()
+ get_session_manager().expire_session(logout.server.providerId)
+ return redirect(logout.msgUrl)
+
+ def soapEndpoint(self):
+ request = get_request()
+ ctype = request.environ.get('CONTENT_TYPE')
+ if not ctype:
+ return
+
+ ctype, ctype_params = parse_header(ctype)
+ if ctype != 'text/xml':
+ return
+
+ response = get_response()
+ response.set_content_type('text/xml')
+
+ length = int(request.environ.get('CONTENT_LENGTH'))
+ soap_message = request.stdin.read(length)
+
+ request_type = lasso.getRequestTypeFromSoapMsg(soap_message)
+
+ if request_type == lasso.REQUEST_TYPE_LOGOUT:
+ logout = lasso.Logout(misc.get_lasso_server())
+ logout.processRequestMsg(soap_message)
+ name_identifier = logout.nameIdentifier.content
+ for session in get_session_manager().values():
+ user = session.get_user(logout.server.providerId)
+ if user and logout.nameIdentifier.content in user.name_identifiers:
+ break
+ else:
+ session = None
+ return self.slo_idp(logout, session)
+
+ if request_type == lasso.REQUEST_TYPE_DEFEDERATION:
+ defederation = lasso.Defederation(misc.get_lasso_server())
+ defederation.processNotificationMsg(soap_message)
+ for session in get_session_manager().values():
+ user = session.get_user(defederation.server.providerId)
+ if user and defederation.nameIdentifier.content in user.name_identifiers:
+ break
+ else:
+ session = None
+ return self.fedterm(defederation, session)
+
+ def federationTermination(self):
+ request = get_request()
+ if not lasso.isLibertyQuery(request.get_query()):
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+
+ defederation = lasso.Defederation(misc.get_lasso_server())
+ defederation.processNotificationMsg(request.get_query())
+ return self.fedterm(defederation, get_session())
+
+ def fedterm(self, defederation, session):
+ if session is not None:
+ host = Host.get_host_with_provider_id(defederation.server.providerId)
+ if host is not None:
+ site_authentication.get_site_authentication(host).local_defederate(session, defederation.server.providerId)
+ if session.lasso_session_dumps.has_key(defederation.server.providerId):
+ defederation.setSessionFromDump(session.lasso_session_dumps[defederation.server.providerId])
+ user = session.get_user(defederation.server.providerId)
+ if user and user.lasso_dumps:
+ defederation.setIdentityFromDump(user.lasso_dumps[0])
+ else:
+ user = None
+
+ try:
+ defederation.validateNotification()
+ except lasso.Error, error:
+ pass # ignore failure (?)
+ else:
+ if user:
+ if not defederation.identity:
+ # if it was the last federation the whole identity dump collapsed
+ del user.lasso_dumps[0]
+ else:
+ user.lasso_dumps[0] = defederation.identity.dump()
+ user.store()
+
+ if user and defederation.nameIdentifier.content:
+ user.remove_name_identifier(defederation.nameIdentifier.content)
+ user.store()
+
+ if defederation.isSessionDirty and session is not None:
+ if not defederation.session:
+ del session.lasso_session_dumps[defederation.server.providerId]
+ else:
+ session.lasso_session_dumps[defederation.server.providerId] = defederation.session.dump()
+ session.store()
+
+ get_session_manager().expire_session(defederation.server.providerId)
+
+ if defederation.msgUrl:
+ return redirect(defederation.msgUrl)
+ else:
+ response = get_response()
+ response.set_status(204)
+ return ''
+
+ def federationTerminationReturn(self):
+ host = Host.get_host_from_url()
+ if host is None:
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+ return redirect(host.get_return_url())
+
+ def local_auth(self):
+ host = Host.get_host_from_url()
+ if host is None:
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+ return site_authentication.get_site_authentication(host).local_auth
+ local_auth = property(local_auth)
+
+ def metadata(self):
+ host = Host.get_host_from_url()
+ if host is None:
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+ get_response().set_content_type('text/xml', 'utf-8')
+ metadata = unicode(open(host.metadata).read(), 'utf-8')
+ return metadata
+
+ def public_key(self):
+ host = Host.get_host_from_url()
+ if host is None:
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+ get_response().set_content_type('text/plain')
+ public_key = open(host.public_key).read()
+ return public_key
diff --git a/larpe/tags/release-1.1.1/larpe/liberty_root.ptl b/larpe/tags/release-1.1.1/larpe/liberty_root.ptl
new file mode 100644
index 0000000..f15c953
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/liberty_root.ptl
@@ -0,0 +1,9 @@
+from quixote.directory import Directory
+from quixote import get_response
+
+from liberty_site import LibertySite
+
+class LibertyRootDirectory(Directory):
+
+ def _q_lookup(self, component):
+ return LibertySite(component)
diff --git a/larpe/tags/release-1.1.1/larpe/liberty_site.ptl b/larpe/tags/release-1.1.1/larpe/liberty_site.ptl
new file mode 100644
index 0000000..b247a4c
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/liberty_site.ptl
@@ -0,0 +1,68 @@
+import sys
+import random
+
+from quixote import get_publisher, get_response, redirect, get_request
+from quixote.directory import Directory
+from quixote.errors import TraversalError
+
+import lasso
+
+import admin
+import liberty
+import saml2
+import idwsf2
+import httplib
+import urllib
+
+from qommon.form import *
+from qommon.misc import get_abs_path, get_current_protocol
+from qommon import template, get_logger
+
+import errors
+import misc
+
+from users import User
+from hosts import Host
+
+class LibertySite(Directory):
+
+ _q_exports = ['', 'login', 'logout', 'liberty', 'saml', 'idwsf2']
+
+ liberty = liberty.Liberty()
+ saml = saml2.Saml2()
+ idwsf2 = idwsf2.IdWsf2()
+
+ def __init__(self, component):
+ self.name = component
+
+ def _q_index (self):
+ raise errors.TraversalError()
+
+ def login [html] (self):
+ get_logger().info('login')
+ get_publisher().reload_cfg()
+
+ if not get_publisher().cfg.has_key('idp'):
+ return template.error_page(_('SSO support is not yet configured'))
+ else:
+ server = misc.get_lasso_server('liberty')
+ if server is not None:
+ return self.liberty.perform_login()
+
+ server = misc.get_lasso_server('saml2')
+ if server is not None:
+ return self.saml.perform_login()
+
+ return template.error_page(_('SSO support is not yet configured'))
+
+ def logout(self):
+ get_logger().info('logout')
+ session = get_session()
+ if not session:
+ return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
+
+ if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
+ return self.saml.slo_sp()
+ else:
+ return self.liberty.singleLogout()
+
diff --git a/larpe/tags/release-1.1.1/larpe/logger.py b/larpe/tags/release-1.1.1/larpe/logger.py
new file mode 100644
index 0000000..3849613
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/logger.py
@@ -0,0 +1,34 @@
+import logging
+
+from quixote import get_request, get_session
+
+from qommon.logger import BotFilter
+
+from hosts import Host
+
+class Formatter(logging.Formatter):
+ def format(self, record):
+ request = get_request()
+
+ record.address = request.get_environ('REMOTE_ADDR', '-')
+ record.path = request.get_path()
+ record.session_id = get_session().get_session_id() or '[nosession]'
+
+ user = None
+ host = Host.get_host_from_url()
+ if not host:
+ host = Host.select(lambda x: x.name == 'larpe')[0]
+ if host:
+ user = get_session().get_user(host.provider_id)
+ if not user:
+ user = get_session().get_user(host.saml2_provider_id)
+
+ if user:
+ user_id = user.id
+ else:
+ user_id = 'unlogged'
+ if BotFilter.is_bot():
+ user_id = 'bot'
+ record.user_id = user_id
+
+ return logging.Formatter.format(self, record)
diff --git a/larpe/tags/release-1.1.1/larpe/misc.py b/larpe/tags/release-1.1.1/larpe/misc.py
new file mode 100644
index 0000000..15492a5
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/misc.py
@@ -0,0 +1,105 @@
+import re
+import os
+
+import lasso
+
+from quixote import get_publisher, get_request
+
+from qommon.misc import get_abs_path
+
+from hosts import Host
+
+def get_root_url():
+ req = get_request()
+ return '%s://%s%s' % (req.get_scheme(), req.get_server(), req.environ['SCRIPT_NAME'])
+
+def get_proxied_site_path():
+ host = Host.get_host_from_url()
+ if host is None:
+ return None
+ return host.site_dir
+
+def get_proxied_site_domain():
+ return get_request().get_server().split(':')[0]
+
+def get_identity_provider_config():
+ get_publisher().reload_cfg()
+ idps_dir = get_abs_path('idp')
+ if get_publisher().cfg.has_key('idp'):
+ idp_dir = os.path.join(idps_dir, get_publisher().cfg['idp'])
+
+ metadata_path = os.path.join(idp_dir, 'metadata.xml')
+
+ public_key_path = os.path.join(idp_dir, 'public_key')
+ if not os.path.isfile(public_key_path):
+ public_key_path = None
+
+ ca_cert_chain_path = os.path.join(idp_dir, 'ca_cert_chain.pem')
+ if not os.path.isfile(ca_cert_chain_path):
+ ca_cert_chain_path = None
+
+ return metadata_path, public_key_path, ca_cert_chain_path
+ return None, None, None
+
+def get_lasso_server(protocol='liberty'):
+ proxied_site_path = get_proxied_site_path()
+ if proxied_site_path is None:
+ return None
+ if protocol == 'liberty':
+ server = lasso.Server(
+ os.path.join(proxied_site_path, 'metadata.xml'),
+ os.path.join(proxied_site_path, 'private_key.pem'),
+ None, None)
+ elif protocol == 'saml2':
+ server = lasso.Server(
+ os.path.join(proxied_site_path, 'saml2_metadata.xml'),
+ os.path.join(proxied_site_path, 'private_key.pem'),
+ None, None)
+ else:
+ raise 'Unknown protocol'
+
+ metadata_path, public_key_path, ca_cert_chain_path = get_identity_provider_config()
+ if metadata_path:
+ try:
+ server.addProvider(
+ lasso.PROVIDER_ROLE_IDP,
+ metadata_path,
+ public_key_path,
+ ca_cert_chain_path)
+ except lasso.Error, error:
+ if error[0] == lasso.SERVER_ERROR_ADD_PROVIDER_PROTOCOL_MISMATCH:
+ return None
+ if error[0] == lasso.SERVER_ERROR_ADD_PROVIDER_FAILED:
+ return None
+ raise
+
+ return server
+
+def get_provider_label(provider):
+ if not provider:
+ return None
+ if not hasattr(provider, str('getOrganization')):
+ return provider.providerId
+
+ organization = provider.getOrganization()
+ if not organization:
+ return provider.providerId
+
+ name = re.findall("(.*?)", organization)
+ if not name:
+ name = re.findall("(.*?)", organization)
+ if not name:
+ return provider.providerId
+ return name[0]
+
+def get_current_protocol():
+ metadata_path, public_key_path, ca_cert_chain_path = get_identity_provider_config()
+ if not metadata_path:
+ return None
+ try:
+ provider = lasso.Provider(lasso.PROVIDER_ROLE_IDP, metadata_path, public_key_path, None)
+ except lasso.Error:
+ return None
+ else:
+ return provider.getProtocolConformance()
+
diff --git a/larpe/tags/release-1.1.1/larpe/plugins/__init__.py b/larpe/tags/release-1.1.1/larpe/plugins/__init__.py
new file mode 100644
index 0000000..0233184
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/plugins/__init__.py
@@ -0,0 +1,3 @@
+from larpe.plugins.site_authentication_plugins import SiteAuthenticationPlugins
+
+site_authentication_plugins = SiteAuthenticationPlugins()
diff --git a/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/__init__.py b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/agirhe.py b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/agirhe.py
new file mode 100644
index 0000000..d3c0f2b
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/agirhe.py
@@ -0,0 +1,142 @@
+import re
+import urllib
+
+from quixote import get_request, get_response, get_session
+
+from qommon.misc import http_post_request
+from qommon.errors import ConnectionError
+from qommon import get_logger
+
+from larpe.qommon.misc import http_get_page
+from larpe.plugins import site_authentication_plugins
+from larpe.site_authentication import SiteAuthentication
+
+class AgirheSiteAuthentication(SiteAuthentication):
+ plugin_name = 'agirhe'
+
+ def auto_detect_site(cls, html_doc):
+ if re.search(
+ """ """, re.DOTALL | re.IGNORECASE)
+ found_forms = regexp.findall(page)
+ if not found_forms:
+ return
+
+ # Get the first form with a password field
+ for found_form in found_forms:
+ regexp = re.compile(
+ """ ]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
+ if regexp.search(found_form) is not None:
+ self.host.auth_form = found_form
+ break
+
+ def parse_other_fields(self):
+ '''Get the default value of all other fields'''
+ self.host.other_fields = {}
+
+ # Get hidden fields
+ regexp = re.compile(
+ """ ]*?type=["']?hidden["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
+ other_fields = regexp.findall(self.host.auth_form)
+
+ # Only get first submit field
+ regexp = re.compile(
+ """ ]*?type=["']?submit["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
+ found = regexp.findall(self.host.auth_form)
+ if found:
+ if other_fields:
+ other_fields.append(found[0])
+ else:
+ other_fields = found[0]
+
+ for field in other_fields:
+ try:
+ regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
+ name = regexp.findall(field)[0]
+ regexp = re.compile("""value=["'](.*?)["'][\s/>]""", re.DOTALL | re.IGNORECASE)
+ value = regexp.findall(field)[0]
+ self.host.other_fields[name] = value
+ if not self.host.post_parameters.has_key(name):
+ self.host.post_parameters[name] = { 'enabled': True,
+ 'value': value,
+ 'immutable': False }
+ except IndexError:
+ continue
+
+site_authentication_plugins.register(AgirheSiteAuthentication)
diff --git a/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/ciril_net_rh.py b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/ciril_net_rh.py
new file mode 100644
index 0000000..417804c
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/ciril_net_rh.py
@@ -0,0 +1,44 @@
+
+import re
+
+from quixote import redirect
+
+from larpe.plugins import site_authentication_plugins
+from larpe.site_authentication import SiteAuthentication
+
+class CirilSiteAuthentication(SiteAuthentication):
+
+ plugin_name = 'ciril'
+ output_filters = ['output_ciril_net_rh']
+
+ def auto_detect_site(cls, html_doc):
+ if re.search(
+ """""",
+ html_doc):
+ return True
+ return False
+ auto_detect_site = classmethod(auto_detect_site)
+
+ def local_auth_check_post(self, username, password, select=None, session_cookies=False):
+ select = select or {}
+ url = self.host.auth_check_url
+
+ # Build request body
+ body = '%s=%s&%s=%s' % (
+ self.host.login_field_name, username, self.host.password_field_name, password)
+ # Add select fields to the body
+ for name, value in select.iteritems():
+ body += '&%s=%s' % (name, value)
+ # Add hidden fields to the body
+ if self.host.send_hidden_fields:
+ for key, value in self.host.other_fields.iteritems():
+ body += '&%s=%s' % (key, value)
+
+ # Build request HTTP headers
+ headers = {'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
+ 'X-Forwarded-Host': self.host.reversed_hostname}
+
+ # Add session id cookie
+ if session_cookies is True:
+ for key, value in self.host.other_fields.iteritems():
+ headers['Cookie'] = 'JSESSIONID=' + value
+
+ # Send request
+ response, status, data, auth_headers = http_post_request(
+ url, body, headers, self.host.use_proxy)
+
+ cookies = response.getheader('Set-Cookie', None)
+ self.host.cookies = []
+ new_session_id = None
+ if cookies is not None:
+ cookies_list = []
+ cookies_set_list = []
+ for cookie in cookies.split(', '):
+ # Drop the path and other attributes
+ cookie_only = cookie.split('; ')[0]
+ regexp = re.compile('=')
+ if regexp.search(cookie_only) is None:
+ continue
+ # Split name and value
+ cookie_split = cookie_only.split('=')
+ cookie_name = cookie_split[0]
+ cookie_value = cookie_split[1]
+ if cookie_name == 'JSESSIONID':
+ new_session_id = cookie_value
+ cookies_list.append('%s=%s' % (cookie_name, cookie_value))
+ set_cookie = '%s=%s; path=/demo' % (cookie_name, cookie_value)
+ cookies_set_list.append(set_cookie)
+ self.host.cookies.append(cookie_name)
+ cookies_headers = '\r\nSet-Cookie: '.join(cookies_set_list)
+ get_response().set_header('Set-Cookie', cookies_headers)
+ self.host.store()
+ get_session().cookies = '; '.join(cookies_list)
+ else:
+ get_logger().warn('No cookie from local authentication')
+
+ if session_cookies is False:
+ # Change idSession hidden field with new session id
+ self.host.other_fields['idSession'] = new_session_id
+ # Retry the request with the new session id
+ return self.local_auth_check_post(username, password, select, session_cookies=True)
+ else:
+ return response.status, data
+
+site_authentication_plugins.register(ConcertoSiteAuthentication)
+
diff --git a/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/egroupware.py b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/egroupware.py
new file mode 100644
index 0000000..9b58634
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/egroupware.py
@@ -0,0 +1,91 @@
+import re
+import urlparse
+
+from quixote import get_request, get_response, get_session
+
+from qommon.misc import http_post_request, http_get_page
+from qommon import get_logger
+
+from larpe.plugins import site_authentication_plugins
+from larpe.site_authentication import SiteAuthentication
+
+class EgroupwareSiteAuthentication(SiteAuthentication):
+ plugin_name = 'egroupware'
+
+ def auto_detect_site(cls, html_doc):
+ if re.search(""" """, html_doc):
+ return True
+ return False
+ auto_detect_site = classmethod(auto_detect_site)
+
+ def local_auth_check_post(self, username, password, select=None):
+ select = select or {}
+ url = self.host.auth_check_url
+
+ # Build request body
+ body = '%s=%s&%s=%s' % (
+ self.host.login_field_name, username, self.host.password_field_name, password)
+ # Add select fields to the body
+ for name, value in select.iteritems():
+ body += '&%s=%s' % (name, value)
+ # Add hidden fields to the body
+ if self.host.send_hidden_fields:
+ for key, value in self.host.other_fields.iteritems():
+ body += '&%s=%s' % (key, value)
+
+ # Build request HTTP headers
+ headers = {'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
+ 'X-Forwarded-Host': self.host.reversed_hostname}
+
+ # Send request
+ response, status, data, auth_headers = http_post_request(
+ url, body, headers, self.host.use_proxy)
+
+ # The specific code is these 2 lines and the called function
+ if self.host.name.startswith('egroupware'):
+ data = self.get_data_after_redirects(response, data)
+
+ cookies = response.getheader('Set-Cookie', None)
+ self.host.cookies = []
+ if cookies is not None:
+ cookies_list = []
+ cookies_set_list = []
+ for cookie in cookies.split(', '):
+ # Drop the path and other attributes
+ cookie_only = cookie.split('; ')[0]
+ regexp = re.compile('=')
+ if regexp.search(cookie_only) is None:
+ continue
+ # Split name and value
+ cookie_split = cookie_only.split('=')
+ cookie_name = cookie_split[0]
+ cookie_value = cookie_split[1]
+ cookies_list.append('%s=%s' % (cookie_name, cookie_value))
+ set_cookie = '%s=%s; path=/' % (cookie_name, cookie_value)
+ cookies_set_list.append(set_cookie)
+ self.host.cookies.append(cookie_name)
+ cookies_headers = '\r\nSet-Cookie: '.join(cookies_set_list)
+ get_response().set_header('Set-Cookie', cookies_headers)
+ self.host.store()
+ get_session().cookies = '; '.join(cookies_list)
+ else:
+ get_logger().warn('No cookie from local authentication')
+
+ return response.status, data
+
+ def get_data_after_redirects(self, response, data):
+ status = response.status
+ headers = {'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
+ 'X-Forwarded-Host': self.host.reversed_hostname}
+ while status == 302:
+ location = response.getheader('Location', None)
+ if location is not None:
+ url_tokens = urlparse.urlparse(self.host.auth_check_url)
+ url = '%s://%s%s'% (url_tokens[0], url_tokens[1], location)
+ response, status, data, auth_headers = http_get_page(
+ url, headers, self.host.use_proxy)
+ return data
+
+site_authentication_plugins.register(EgroupwareSiteAuthentication)
+
diff --git a/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/sympa.py b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/sympa.py
new file mode 100644
index 0000000..734ba23
--- /dev/null
+++ b/larpe/tags/release-1.1.1/larpe/plugins/site_authentication/sympa.py
@@ -0,0 +1,45 @@
+import re
+
+from quixote import get_response, redirect
+from quixote.html import htmltext
+
+from larpe.plugins import site_authentication_plugins
+from larpe.site_authentication import SiteAuthentication
+
+class SympaSiteAuthentication(SiteAuthentication):
+ plugin_name = 'sympa'
+
+ def auto_detect_site(cls, html_doc):
+ if re.search("""