1.0 release

git-svn-id: svn+ssh://labs.libre-entreprise.org/svnroot/larpe@461 3ed937ae-f919-0410-9a43-8e6f19e4ba6e
This commit is contained in:
dlaniel 2009-03-09 14:54:38 +00:00
parent d6e10f1836
commit 4dc72b40aa
172 changed files with 21697 additions and 0 deletions

View File

@ -0,0 +1,5 @@
Damien Laniel <dlaniel@entrouvert.com>
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.

View File

@ -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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

101
larpe/tags/release-1.0/TODO Normal file
View File

@ -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

View File

@ -0,0 +1,12 @@
<VirtualHost *>
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
</VirtualHost>
include /var/lib/larpe/vhosts.d

View File

@ -0,0 +1,19 @@
# Static files
DocumentRoot /usr/share/larpe/web/
# Python application
SCGIMount / 127.0.0.1:3007
# Static files for larpe
<Location /larpe/>
ProxyPass !
SCGIHandler off
</Location>
# Larpe python application
<Location /liberty/>
ProxyPass !
</Location>
# No gzip compression
RequestHeader unset Accept-Encoding

View File

@ -0,0 +1,30 @@
import re
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()

View File

@ -0,0 +1,63 @@
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 <dlaniel@entrouvert.com> Mon, 09 Mar 2009 11:19:49 +0100
larpe (0.2.1-1) unstable; urgency=low
* New release
-- Damien Laniel <dlaniel@entrouvert.com> Wed, 20 Jun 2007 15:43:16 +0200
larpe (0.2.0-1) unstable; urgency=low
* New release
-- Damien Laniel <dlaniel@entrouvert.com> Tue, 30 Jan 2007 18:07:04 +0100
larpe (0.1.1-2) unstable; urgency=low
* Use python2.4
-- Damien Laniel <dlaniel@entrouvert.com> Tue, 19 Dec 2006 17:21:05 +0100
larpe (0.1.1-1) unstable; urgency=low
* New release
-- Damien Laniel <dlaniel@entrouvert.com> Thu, 5 Oct 2006 11:47:53 +0200
larpe (0.1.0-1) unstable; urgency=low
* New release
-- Damien Laniel <dlaniel@entrouvert.com> 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 <dlaniel@entrouvert.com> 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 <dlaniel@entrouvert.com> Mon, 25 Sep 2006 11:11:36 +0200
larpe (0.0.2-1) unstable; urgency=low
* Initial package.
-- Damien Laniel <dlaniel@entrouvert.com> Fri, 08 Sep 2006 16:00:00 +0200

View File

@ -0,0 +1 @@
5

View File

@ -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

View File

@ -0,0 +1,17 @@
Source: larpe
Section: web
Priority: optional
Maintainer: Damien Laniel <dlaniel@entrouvert.com>
Build-Depends: debhelper (>= 5.0.37.2), python, python-central (>= 0.5), gettext
Standards-Version: 3.7.2.0
XS-Python-Version: current
Package: larpe
Architecture: any
XB-Python-Version: ${python:Versions}
Depends: ${python:Depends}, python-quixote | quixote (>= 2.0), python-lasso (>= 0.6.5), python-scgi, python-libxml2, apache2, libapache2-mod-scgi, libapache2-mod-python, libapache2-mod-proxy-html
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.
.

View File

@ -0,0 +1,27 @@
This package was debianized by Damien Laniel <dlaniel@entrouvert.com> on
Fri, 08 Sep 2006 16:00:00 +0200.
Upstream Author: Damien Laniel <dlaniel@entrouvert.com>
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'.

View File

@ -0,0 +1,4 @@
etc/apache2/sites-available
etc/larpe
usr/sbin
var/lib/larpe

View File

@ -0,0 +1,2 @@
README
AUTHORS

View File

@ -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

View File

@ -0,0 +1,3 @@
#!/bin/sh
/etc/init.d/apache2 reload

View File

@ -0,0 +1,66 @@
#! /bin/sh
# postinst script for larpe
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <failed-install-package> <version> `removing'
# <conflicting-package> <version>
# 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

View File

@ -0,0 +1,41 @@
#! /bin/sh
# prerm script for larpe
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <prerm> `remove'
# * <old-prerm> `upgrade' <new-version>
# * <new-prerm> `failed-upgrade' <old-version>
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
# * <deconfigured's-prerm> `deconfigure' `in-favour'
# <package-being-installed> <version> `removing'
# <conflicting-package> <version>
# 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

View File

@ -0,0 +1 @@
2

View File

@ -0,0 +1,73 @@
#!/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/python2.4
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_pycentral
dh_installdeb
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install

View File

@ -0,0 +1,37 @@
Template: larpe/hostname
Type: string
Default: localhost
Description: Hostname :
This is the name by which this reverse-proxy will be known on the network.
Your DNS server must have been configured accordingly.
Template: larpe/enable_vhost
Type: boolean
Default: false
Description: Enable this vhost :
A new virtual host for Larpe will be created with the hostname you chose. It may break
your Apache2 configuration.
.
If you didn't tweak your Apache2 configuration a lot
and you don't have vital websites on the same server, you can safely say "yes" here
to enable it, and fix it later if needed.
.
If you prefer checking this vhost will fit well with your Apache2 configuration first,
and enable it by yourself later, say "no".
Template: larpe/admin_username
Type: string
Default: admin
Description: Administrator login :
This is the login which will be used to connect to the administrator interface
Template: larpe/admin_password
Type: password
Description: Administrator password :
This is the password which will be used to connect to the administrator interface
Template: larpe/admin_email
Type: string
Default: root@localhost
Description: Administrator email address :
This is the email address to which problem reports will be sent

View File

@ -0,0 +1,8 @@
all:
$(MAKE) -C en
clean:
$(MAKE) -C en clean
.PHONY: clean

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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 "<VirtualHost \*>" 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/

View File

@ -0,0 +1,5 @@
#! /bin/sh
size=$(identify $1 | cut -d ' ' -f 3)
composite $1 -size $(identify $1 | cut -d ' ' -f3) xc:white $2

View File

@ -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

View File

@ -0,0 +1,12 @@
.svn
*.pyc
*.pyo
*.pye
*.ptle
*.swp
debian.sarge
make_debian_package.sh
build
dist
tests
larpe/filter

View File

@ -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

View File

@ -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

View File

@ -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 <jmliger@siris.sorbonne.fr> 0.2.9-2
- Added missing BuildRequires gettext
- Enabled larpe init script
* Mon Jan 19 2009 Damien Laniel <dlaniel@entrouvert.com> 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 <jmliger@siris.sorbonne.fr> 0.2.1-2
- Added missing BuildRequires tetex-latex for doc subpackage
- Rebuilt on CentOS 4,5
* Wed Jan 14 2009 Jean-Marc Liger <jmliger@siris.sorbonne.fr> 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 <jmliger@siris.sorbonne.fr> 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 <jmliger@siris.sorbonne.fr> 0.1.0-1
- First 0.1.0
- Built on Fedora Core 3 / RHEL 4 and Fedora Core 6 / RHEL 5

View File

@ -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 <space>, <tab>, and <newline>. 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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
/* 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);
}

View File

@ -0,0 +1,10 @@
'''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'
OUTPUT_FILTER_BASE = '/usr/share/larpe/output_filter_base.py'

View File

@ -0,0 +1,16 @@
'''Setup path and load Lasso library'''
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

View File

@ -0,0 +1 @@
from root import RootDirectory

View File

@ -0,0 +1,302 @@
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('<VirtualHost (.*?)>')
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('<VirtualHost %(ip_port)s>' % 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('</VirtualHost>\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 hasattr(self.host, 'apache_output_python_filters') and \
hasattr(self.host, 'apache_python_paths') 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 hasattr(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 hasattr(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<Location %(reversed_directory)s>' % 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)
# Enable rewrite module
if self.old_auth_url is not None or self.old_logout_url is not None:
conf_lines.append('RewriteEngine On')
# 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(
'RewriteRule %(old_auth_url)s %(new_auth_url)s [R]' % self.cfg)
# Redirect old logout url to the new one
if self.old_logout_url is not None:
conf_lines.append(
'RewriteRule %(old_logout_url)s %(new_logout_url)s [R]' % self.cfg)
# Redirect the home page to the login page
if self.host.redirect_root_to_login is True:
conf_lines.append('RewriteRule /$ %(new_auth_url)s [R]' % 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</Location>\n')

View File

@ -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'))
'<h2>%s</h2>' % _('Edit')
form.render()
def delete [html] (self):
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'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'))
'<h2>%s : %s</h2>' % (_('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'))
"""<ul id="nav-fields-admin">
<li><a href="new">%s</a></li>
</ul>""" % _('New Field')
'<ul class="biglist">'
for field_prefill in FieldPrefill.select(lambda x: x.form_id == self.form_prefill.id):
if not field_prefill.name:
continue
# Split too long xpath
xpath = field_prefill.xpath
xpath_tokens = xpath.split(str('/'))
if len(xpath_tokens) > 3:
xpath = str('.../') + str('/').join(xpath_tokens[-3:])
'<li>'
'<strong class="label">%s</strong>' % field_prefill.name
'<br />%s' % xpath
'<p class="commands">'
command_icon('%s/' % field_prefill.id, 'edit')
command_icon('%s/delete' % field_prefill.id, 'remove')
'</p></li>'
'</ul>'
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)

View File

@ -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')
'<h2>%s</h2>' % _('Form prefilling configuration')
'<dl>'
'<dt><a href="edit">%s</a></dt> <dd>%s</dd>' % (
_('Edit'), _('Configure this form'))
'<dt><a href="fields/">%s</a></dt> <dd>%s</dd>' % (
_('Fields'), _('Configure the fields of this form'))
'</dl>'
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'))
'<h2>%s</h2>' % _('Edit')
form.render()
def delete [html] (self):
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'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'))
'<h2>%s : %s</h2>' % (_('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'))
"""<ul id="nav-forms-admin">
<li><a href="new">%s</a></li>
</ul>""" % _('New Form')
'<ul class="biglist">'
for form_prefill in FormPrefill.select(lambda x: x.host_id == self.host.id):
if not form_prefill.name:
continue
'<li>'
'<strong class="label">%s</strong>' % form_prefill.name
url = form_prefill.url
'<br /><a href="%s">%s</a>' % (url, url)
'<p class="commands">'
command_icon('%s/' % form_prefill.id, 'edit')
command_icon('%s/delete' % form_prefill.id, 'remove')
'</p></li>'
'</ul>'
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)

File diff suppressed because it is too large Load Diff

View File

@ -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 = """\
<?xml version="1.0"?>
<EntityDescriptor
providerID="%(provider_id)s"
xmlns="urn:liberty:metadata:2003-08">""" % cfg
sp_head = """
<SPDescriptor protocolSupportEnumeration="urn:liberty:iff:2003-08">"""
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 = """
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data><ds:X509Certificate>%s</ds:X509Certificate></ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>""" % cfg['signing_public_key']
elif 'KEY' in cfg['signing_public_key']:
signing_public_key = """
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>%s</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>""" % cfg['signing_public_key']
sp_body = """
<AssertionConsumerServiceURL id="AssertionConsumerServiceURL1" isDefault="true">%(base_url)s/assertionConsumer</AssertionConsumerServiceURL>
<SoapEndpoint>%(base_url)s/soapEndpoint</SoapEndpoint>
<SingleLogoutServiceURL>%(base_url)s/singleLogout</SingleLogoutServiceURL>
<SingleLogoutServiceReturnURL>%(base_url)s/singleLogoutReturn</SingleLogoutServiceReturnURL>
<SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-idp-http</SingleLogoutProtocolProfile>
<SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-sp-soap</SingleLogoutProtocolProfile>
<SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-sp-http</SingleLogoutProtocolProfile>
<FederationTerminationServiceURL>%(base_url)s/federationTermination</FederationTerminationServiceURL>
<FederationTerminationServiceReturnURL>%(base_url)s/federationTerminationReturn</FederationTerminationServiceReturnURL>
<FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-idp-soap</FederationTerminationNotificationProtocolProfile>
<FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-idp-http</FederationTerminationNotificationProtocolProfile>
<FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-sp-soap</FederationTerminationNotificationProtocolProfile>
<FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-sp-http</FederationTerminationNotificationProtocolProfile>
<AuthnRequestsSigned>true</AuthnRequestsSigned>
</SPDescriptor>""" % cfg
orga = ''
if cfg.get('organization_name'):
orga = """
<Organization>
<OrganizationName>%s</OrganizationName>
</Organization>""" % unicode(cfg['organization_name'], 'iso-8859-1').encode('utf-8')
epilogue = """
</EntityDescriptor>"""
return '\n'.join([prologue, sp_head, signing_public_key, sp_body, orga, epilogue])
def get_saml2_metadata(cfg):
prologue = """\
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
entityID="%(saml2_provider_id)s">""" % cfg
sp_head = """
<SPSSODescriptor
AuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">"""
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 = """
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data><ds:X509Certificate>%s</ds:X509Certificate></ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>""" % cfg['signing_public_key']
elif 'KEY' in cfg['signing_public_key']:
signing_public_key = """
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>%s</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>""" % cfg['signing_public_key']
sp_body = """
<AssertionConsumerService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
Location="%(saml2_base_url)s/singleSignOnArtifact" />
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="%(saml2_base_url)s/singleLogoutSOAP" />
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="%(saml2_base_url)s/singleLogout"
ResponseLocation="%(saml2_base_url)s/singleLogoutReturn" />
</SPSSODescriptor>""" % cfg
orga = ''
if cfg.get('organization_name'):
orga = """
<Organization>
<OrganizationName>%s</OrganizationName>
</Organization>""" % unicode(cfg['organization_name'], 'iso-8859-1').encode('utf-8')
epilogue = """
</EntityDescriptor>"""
return '\n'.join([prologue, sp_head, signing_public_key, sp_body, orga, epilogue])

View File

@ -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] ():
"""<p>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.</p>
<p>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.</p>
<p>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.</p>
"""
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()

View File

@ -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'))
"<h2>%s</h2>" % _('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'))
'<h2>%s</h2>' % _('Emails')
'<ul>'
'<li><a href="options">%s</a></li>' % _('General Options')
'</ul>'
'<p>'
'<a href="..">%s</a>' % _('Back')
'</p>'
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:
'<h2>%s</h2>' % _('Liberty Alliance & SAML 2.0 Service Provider')
else:
'<h2>%s</h2>' % _('Liberty Alliance Service Provider')
'<dl> <dt><a href="liberty_sp">%s</a></dt> <dd>%s</dd>' % (
_('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
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
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
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
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
'<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
public_key_url,
_('Public key'),
_('Download SSL Public Key file'))
if lasso.SAML2_SUPPORT:
'<h2>%s</h2>' % _('Liberty Alliance & SAML 2.0 Identity Provider')
else:
'<h2>%s</h2>' % _('Liberty Alliance Identity Provider')
'<dl>'
'<dt><a href="liberty_idp/">%s</a></dt> <dd>%s</dd>' % (
_('Identity Provider'), _('Configure an identity provider'))
if get_publisher().cfg.has_key('idp'):
'<dt><a href="liberty_idp/metadata.xml">%s</a></dt> <dd>%s</dd>' % (
_('Identity Provider metadatas'), _('See current identity provider metadatas'))
'</dl>'
'<h2>%s</h2>' % _('Global parameters for the sites')
'<dl>'
'<dt><a href="domain_names">%s</a></dt> <dd>%s</dd>' % (
_('Domain name'), _('Configure the base domain name for the sites'))
'<dt><a href="apache2_configuration_generation">%s</a></dt> <dd>%s</dd>' % (
_('Apache 2 configuration generation'), _('Customise Apache 2 configuration generation'))
'<dt><a href="proxy">%s</a></dt> <dd>%s</dd>' % (
_('Proxy'), _('Connect to the sites through a web proxy'))
'</dl>'
'<h2>%s</h2>' % _('Customisation')
'<dl>'
'<dt><a href="language">%s</a></dt> <dd>%s</dd>' % (
_('Language'), _('Configure site language'))
'<dt><a href="emails/">%s</a></dt> <dd>%s</dd>' % (
_('Emails'), _('Configure email settings'))
'</dl>'
'<h2>%s</h2>' % _('Misc')
'<dl>'
'<dt><a href="debug_options">%s</a></dt> <dd>%s</dd>' % (
_('Debug Options'), _('Configure options useful for debugging'))
'</dl>'
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'))
'<h2>%s</h2>' % _('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'))
'<h2>%s</h2>' % _('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'))
'<h2>%s</h2>' % _('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()

View File

@ -0,0 +1,275 @@
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.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))
'<h2>%s - %s</h2>' % (_('User'), self.user.name)
'<div class="form">'
'<div class="title">%s</div>' % _('Name')
'<div class="StringWidget content">%s</div>' % self.user.name
if self.user.email:
'<div class="title">%s</div>' % _('Email')
'<div class="StringWidget content">%s</div>' % self.user.email
# if self.user.lasso_dump:
# identity = lasso.Identity.newFromDump(self.user.lasso_dump)
# server = misc.get_lasso_server()
# if len(identity.providerIds) and server:
# '<h3>%s</h3>' % _('Liberty Alliance Details')
# '<div class="StringWidget content"><ul>'
# for pid in identity.providerIds:
# provider = server.getProvider(pid)
# label = misc.get_provider_label(provider)
# if label:
# label = '%s (%s)' % (label, pid)
# else:
# label = pid
# federation = identity.getFederation(pid)
# '<li>'
# _('Account federated with %s') % label
# '<br />'
# if federation.localNameIdentifier:
# _("local: ") + federation.localNameIdentifier.content
# if federation.remoteNameIdentifier:
# _("remote: ") + federation.remoteNameIdentifier.content
# '</li>'
# '</ul></div>'
# # XXX: only display this in debug mode:
# '<h4>%s</h4>' % _('Lasso Identity Dump')
# '<pre>%s</pre>' % self.user.lasso_dump
'</div>'
def debug [html] (self):
get_response().breadcrumb.append( ('debug', _('Debug')) )
html_top('users', 'Debug')
"<h2>Debug - %s</h2>" % self.user.name
"<pre>"
self.user.lasso_dump
"</pre>"
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'))
'<h2>%s</h2>' % _('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('<p>%s</p>' % _(
"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'))
'<h2>%s %s</h2>' % (_('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'))
'<h2>%s</h2>' % _('Identification Token')
'<p>%s</p>' % _('You are about to generate a token than can be used to federate the account.')
'<p>%s</p>' % _('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:
'<p>%s</p>' % _('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()
'<p>'
_('Identification Token for %s') % self.user.name
' : %s</p>' % 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 errors.EmailError, e:
html_top('users', title = _('Identification Token'))
_('Failed sending email. Check your email configuration.')
'<div class="buttons"><a href=".."><input type="button" value="%s" /></a></div><br />' % _('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')):
'<p>%s</p>' % _('Liberty support must be setup before creating users.')
else:
"""<ul id="nav-users-admin">
<li><a href="new">%s</a></li>
</ul>""" % _('New User')
debug_cfg = get_publisher().cfg.get('debug', {})
users = User.select(lambda x: x.name is not None, order_by = 'name')
'<ul class="biglist">'
for user in users:
'<li>'
'<strong class="label">%s</strong>' % user.name
if user.email:
'<p class="details">'
user.email
'</p>'
'<p class="commands">'
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')
'</p></li>'
'</ul>'
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'))
'<h2>%s</h2>' % _('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()

View File

@ -0,0 +1,2 @@
from start import start

View File

@ -0,0 +1,34 @@
import socket
import sys
from qommon.scgi_server import run
import publisher
def start(args):
port = 3007
script_name = ''
i = 0
while i < len(args):
if args[i] == '--port':
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':
script_name = args[i+1]
i += 1
i += 1
try:
run(publisher.LarpePublisher.create_publisher, port=port, script_name=script_name)
except socket.error, err:
if err[0] == 98:
print >> sys.stderr, 'address already in use'
sys.exit(1)
raise
except KeyboardInterrupt:
sys.exit(1)

View File

@ -0,0 +1,14 @@
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
login_url = '%s/liberty/larpe/login' % request.environ['SCRIPT_NAME']
redirect(login_url)

View File

@ -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)

View File

@ -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 = {}

View File

@ -0,0 +1,164 @@
import os
import re
from mod_python import apache
#import larpe.hosts
app_dir = '/var/lib/larpe'
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 not None and not is_html:
filter.pass_on()
else:
if not hasattr(filter.req, 'temp_doc'): # the start
filter.req.temp_doc = [] # create new attribute to hold document
# If content-length ended up wrong, Gecko browsers truncated data, so
if 'Content-Length' in filter.req.headers_out:
del filter.req.headers_out['Content-Length']
# filter.write(filter.req.headers_in['Cookie'])
# delete_cookies(filter)
#filter.req.headers_out['Set-Cookie'] = 'dc_admin="deleted"; max-age=0; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/'
temp_doc = filter.req.temp_doc
s = filter.read()
while s: # could get '' at any point, but only get None at end
temp_doc.append(s)
s = filter.read()
if s is None: # the end
page = ''.join(temp_doc)
page = filter_dispatch(filter, page)
filter.write(page)
filter.close()
def filter_dispatch(filter, page):
# host = get_host_from_url(filter)
# if host is None:
# apache.log_error('Host not found')
# return page
try:
# function_name = 'filter_' + host.label.lowercase()
host_name = filter.req.hostname.split('.')[-3]
function_name = 'filter_' + host_name
return eval(function_name + '(filter, page)')
except:
return page
# return filter_default(filter, page)
def filter_default(filter, page):
# host = get_host_from_url(filter)
# if host is None:
# apache.log_error('Host not found')
# return page
# if host.auth_url is not None or host.auth_form is None:
# return page
form = find_auth_form(page)
if form is not None:
try:
host_name = filter.req.hostname.split('.')[-3]
return page.replace(form, """
<form method="post" action="/liberty/%s/login">
<input type="submit" value="Connexion" />
</form>""" % host_name)
except:
pass
return page
def find_auth_form(page):
regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
found_forms = regexp.findall(page)
for found_form in found_forms:
regexp = re.compile("""<input[^>]*?type="password"[^>]*?>""", re.DOTALL | re.IGNORECASE)
if regexp.search(found_form) is not None:
return found_form
return None
#def get_host_from_url(filter):
# try:
# return list(Host.select(lambda x: x.reversed_hostname == filter.req.hostname \
# and x.reversed_directory == get_proxied_site_name(filter)))[0]
# except:
# return None
def filter_linuxfr(filter, page):
str_to_replace = re.compile(str('<form method="post" action="/login.html" id="formulaire">.*?</form>'), re.DOTALL)
return str_to_replace.sub(str(r"""<form method="post" action="/liberty/linuxfr/login" id="formulaire">
<div style="text-align: center; font-size: 13px;" class="loginbox">
<input type="submit" value="Connexion" />
<br />
<a href="/user_new.html">Cr&eacute;er un compte</a>
</div>
</form>"""
), page)
def filter_dotclear(filter, page):
if filter.req.uri == '/dot/ecrire/redac_list.php':
str_to_replace = re.compile(str('(\[[^\?]+\?id=)([^"]+)(">[^\]]*)\]'), re.DOTALL)
return str_to_replace.sub(str(r'\1\2\3 - <a href="/liberty/dot/admin_token?id=\2">token</a> ]'), page)
if filter.req.uri == '/dot/ecrire/redacteur.php':
str_to_replace = re.compile(str('(<form action=")redacteur.php'))
page = str_to_replace.sub(str(r'\1/liberty/dot/admin_new_user'), page)
str_to_replace = re.compile(str('<p class="field"><label class="float" for="user_pwd">.*?</p>'), re.DOTALL)
return str_to_replace.sub(r'', page)
return page
def filter_concerto(filter, page):
str_to_replace = re.compile(str('<form action="login.do" method="post">.*?</form>'), re.DOTALL)
return str_to_replace.sub(str(r"""<form method="post" action="/liberty/concerto/login" id="formulaire">
<div style="text-align: center; font-size: 13px;" class="loginbox">
<input type="submit" value="Connexion" />
</div>
</form>"""
), page)
def get_abs_path(s):
if not s:
return s
if s[0] == '/':
return s
return os.path.join(app_dir, s)
def get_proxied_site_path(filter):
proxy_domain_name = filter.req.hostname
proxied_site_dir = get_proxied_site_name(filter)
return get_abs_path(os.path.join('sp', proxy_domain_name, proxied_site_dir))
def get_proxied_site_name(filter):
uri_tokens = filter.req.uri.split('/')
if uri_tokens[1] != 'liberty':
return uri_tokens[1]
return uri_tokens[2]
def delete_cookies(filter):
success = False
cookies_file_name = get_abs_path(os.path.join(get_proxied_site_path(filter), 'cookies_to_delete'))
cookies_file = open(cookies_file_name, 'r')
for cookie in cookies_file.read().split():
if filter.req.headers_in.has_key('Cookie'):
cookies_header = filter.req.headers_in['Cookie']
# filter.req.temp_doc.append(filter.req.headers_in['Cookie'])
#filter.req.temp_doc.append(cookie[len(cookie) -1:])
cookies_match = re.findall(cookie[:len(cookie) -1], cookies_header)
if len(cookies_match) > 0:
filter.req.temp_doc.append('User-Agent')
# filter.req.temp_doc.append('%s="deleted"; max-age=0; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/' % cookie.split('=')[0])
filter.req.headers_out['Set-Cookie'] = '%s="deleted"; max-age=0; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/' % cookie.split('=')[0]
# cookies_file.close()
# cookies_file = open(cookies_file_name, 'w')
break
# else:
# filter.req.temp_doc.append('dommage')
cookies_file.close()
# if success:
# cookies_file = open(cookies_file_name, 'w')
# cookies_file.close()

View File

@ -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

View File

@ -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)

View File

@ -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 + '<script type="text/javascript">alert("%s")</script>' % \
_('Failed getting attributes from the attribute provider.')
except:
return page + '<script type="text/javascript">alert("%s")</script>' % \
_('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('(.*)(<input[^>]*? id="%s".*?>)(.*)' % field_name,
re.DOTALL | re.IGNORECASE)
match = regex.match(page)
if not match:
regex = re.compile('(.*)(<input[^>]*? 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('<input', '<input value="%s"' % new_value)
page = ''.join([before, input_field, after])
continue
# Textarea
regex = re.compile('(.*<textarea[^>]*? id="%s".*?>)[^<]*(</textarea>.*)' % field_name,
re.DOTALL | re.IGNORECASE)
match = regex.match(page)
if not match:
regex = re.compile('(.*<textarea[^>]*? name="%s".*?>)[^<]*(</textarea>.*)' % 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('(.*<select[^>]*? id="%s".*?>)(.*?)(</select>.*)' % field_name,
re.DOTALL | re.IGNORECASE)
match = regex.match(page)
if not match:
regex = re.compile('(.*<select[^>]*? name="%s".*?>)(.*?)(</select>.*)' % 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('(.*<option[^>]*? value="%s".*?)(>[^<]*</option>.*)' % new_value,
re.DOTALL | re.IGNORECASE)
match2 = regex2.match(options)
if match2:
before2, after2 = match2.groups()
regex3 = re.compile('(.*<option[^>]*?)( selected(="selected")?)(.*?>[^<]*</option>.*)',
re.DOTALL | re.IGNORECASE)
match3 = regex3.match(options)
if match3:
before3, selected, selected_value, after3 = match3.groups()
options = ''.join([before3, after3])
regex2 = re.compile('(.*<option[^>]*? value="%s".*?)(>[^<]*</option>.*)' % 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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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("<OrganizationDisplayName.*>(.*?)</OrganizationDisplayName>", organization)
if not name:
name = re.findall("<OrganizationName.*>(.*?)</OrganizationName>", 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()

View File

@ -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
import site_authentication
class AgirheSiteAuthentication(site_authentication.SiteAuthentication):
plugin_name = 'agirhe'
def auto_detect_site(cls, html_doc):
if re.search(
"""<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/""",
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)
# Get the authentication page
try:
response, status, page, auth_headers = http_get_page(
self.host.auth_form_url, use_proxy=self.host.use_proxy)
except ConnectionError, err:
get_logger().warn(err)
return None, None
# Get current hidden fields everytime
self.parse_forms(page)
if self.host.auth_form is not None:
self.parse_other_fields()
# Add hidden fields to the body
for key, value in self.host.other_fields.iteritems():
value = urllib.quote_plus(value)
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)
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
# The 3 following functions have been copied from admin/hosts.ptl
def parse_forms(self, page):
'''Search for an authentication form'''
# Get all forms
regexp = re.compile("""<form.*?</form>""", 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(
"""<input[^>]*?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(
"""<input[^>]*?type=["']?hidden["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
other_fields = regexp.findall(self.host.auth_form)
# Only get first submit field
regexp = re.compile(
"""<input[^>]*?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.register_site_authentication_class(AgirheSiteAuthentication)

View File

@ -0,0 +1,38 @@
import re
from quixote import redirect
import site_authentication
class CirilSiteAuthentication(site_authentication.SiteAuthentication):
plugin_name = 'ciril'
def auto_detect_site(cls, html_doc):
if re.search(
"""<form name="myForm" id="myForm" method="post" target="choixAppli" action="/cgi-bin/acces.exe" """,
html_doc):
return True
return False
auto_detect_site = classmethod(auto_detect_site)
def check_auth(self, status, data):
success = False
return_content = ''
# If status is 500, fail without checking other criterias
if status // 100 == 5:
success = False
return_content = redirect(self.host.get_return_url())
regexp = re.compile(
"""javascript\:window\.open\('(/net_rh/accueil.php\?.*?)', '_blank'\)""",
re.DOTALL | re.IGNORECASE)
match = regexp.findall(data)
if match:
success = True
return_content = redirect(match[0])
return success, return_content
site_authentication.register_site_authentication_class(CirilSiteAuthentication)

View File

@ -0,0 +1,88 @@
import re
from quixote import get_request, get_response, get_session
from qommon.misc import http_post_request
from qommon import get_logger
import site_authentication
class ConcertoSiteAuthentication(site_authentication.SiteAuthentication):
plugin_name = 'concerto'
def auto_detect_site(cls, html_doc):
if re.search(
"""<meta name="description" content="Page d'accueil du site Espace-Famille" />""",
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.register_site_authentication_class(ConcertoSiteAuthentication)

View File

@ -0,0 +1,90 @@
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
import site_authentication
class EgroupwareSiteAuthentication(site_authentication.SiteAuthentication):
plugin_name = 'egroupware'
def auto_detect_site(cls, html_doc):
if re.search("""<meta name="description" content="eGroupWare" />""", 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.register_site_authentication_class(EgroupwareSiteAuthentication)

View File

@ -0,0 +1,44 @@
import re
from quixote import get_response, redirect
from quixote.html import htmltext
import site_authentication
class SympaSiteAuthentication(site_authentication.SiteAuthentication):
plugin_name = 'sympa'
def auto_detect_site(cls, html_doc):
if re.search("""<FORM ACTION="/wwsympa.fcgi" METHOD=POST>""", html_doc):
return True
return False
auto_detect_site = classmethod(auto_detect_site)
def check_auth(self, status, data):
success = False
return_content = ''
if self.host.auth_system == 'password':
# If there is a password field, authentication probably failed
regexp = re.compile(
"""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
if not regexp.findall(data):
success = True
# The specific part is only these 2 lines
get_response().filter.update({'no_template': True})
return_content = htmltext(data)
elif self.host.auth_system == 'status':
match_status = int(self.host.auth_match_status)
if match_status == status:
success = True
return_content = redirect(self.host.return_url)
elif self.host.auth_system == 'match_text':
# If the auth_match_text is not matched, it means the authentication is successful
regexp = re.compile(self.host.auth_match_text, re.DOTALL)
if not regexp.findall(data):
success = True
return_content = redirect(self.host.get_return_url())
return success, return_content
site_authentication.register_site_authentication_class(SympaSiteAuthentication)

View File

@ -0,0 +1,53 @@
import os
import cPickle
from quixote import get_request
from Defaults import *
from qommon.publisher import set_publisher_class, QommonPublisher
from root import RootDirectory
from admin import RootDirectory as AdminRootDirectory
from sessions import StorageSessionManager
from users import User
class LarpePublisher(QommonPublisher):
APP_NAME = 'larpe'
APP_DIR = APP_DIR
DATA_DIR = DATA_DIR
ERROR_LOG = ERROR_LOG
WEB_ROOT = WEB_ROOT
supported_languages = ['fr']
root_directory_class = RootDirectory
admin_directory_class = AdminRootDirectory
session_manager_class = StorageSessionManager
user_class = User
def get_application_static_files_root_url(self):
return '%s/%s/' % (get_request().environ['SCRIPT_NAME'], WEB_ROOT)
def set_app_dir(self, request):
self.app_dir = os.path.join(self.APP_DIR, request.get_server().lower().split(':')[0])
self.reload_cfg()
if self.cfg.has_key('proxy_hostname'):
self.app_dir = os.path.join(self.APP_DIR, self.cfg['proxy_hostname'])
if self.app_dir is not None and not os.path.exists(self.app_dir):
os.mkdir(self.app_dir)
return True
cfg = None
def write_cfg(self, directory=None):
dump = cPickle.dumps(self.cfg)
if directory is None:
directory = self.app_dir
filename = os.path.join(directory, 'config.pck')
open(filename, 'w').write(dump)
set_publisher_class(LarpePublisher)
extra_dir = os.path.join(os.path.dirname(__file__), 'plugins','site_authentication')
LarpePublisher.register_extra_dir(extra_dir)

View File

@ -0,0 +1,106 @@
import os
import httplib
import lasso
from quixote import get_request, get_response, get_session, redirect
from quixote.directory import Directory
from qommon.form import *
from qommon import template
import admin
import liberty_root
import errors
from hosts import Host
from users import User
from Defaults import WEB_ROOT
class RootDirectory(Directory):
_q_exports = ['', 'admin', 'liberty', 'logout', 'token']
admin = admin.RootDirectory()
liberty = liberty_root.LibertyRootDirectory()
def _q_index [html] (self):
template.html_top(_('Welcome to Larpe reverse proxy'))
'<ul><li><a href="%s/admin/">%s</a></li></ul>' % (get_request().environ['SCRIPT_NAME'],
_('Configure Larpe'))
def _q_traverse(self, path):
response = get_response()
response.filter = {}
return Directory._q_traverse(self, path)
def _q_lookup(self, component):
return redirect(component + '/')
def logout(self):
return redirect(get_publisher().get_root_url() + 'liberty/larpe/logout')
def token [html] (self):
session = get_session()
if not session.name_identifier or not session.lasso_anonymous_identity_dump:
raise errors.AccessUnauthorizedError()
# If the token is in the query string, use it
query_string = get_request().get_query()
if query_string:
parameters = query_string.split(str('&'))
for param in parameters:
values = param.split(str('='))
if len(values) < 2:
continue
if values[0] == str('token'):
return self._federate_token(values[1])
# Otherwise, display a form to ask for the token
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'token', title = _('Identification Token'),
required = True, size = 30)
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():
template.html_top(_('Identification Token'))
'<p>'
_('Please enter your identification token. ')
_('Your local account will be federated with your Liberty Alliance account.')
'</p>'
form.render()
else:
token = form.get_widget('token').parse()
return self._federate_token(token)
def _federate_token(self, token):
session = get_session()
# Get the user who owns this token
users_with_token = list(User.select(lambda x: x.identification_token == token))
if len(users_with_token) == 0:
return template.error_page(_('Unknown Token'))
# Fill user attributes
user = users_with_token[0]
user.name_identifiers = [ session.name_identifier ]
user.lasso_dumps = [ session.lasso_anonymous_identity_dump ]
user.identification_token = None
user.is_admin = True
user.store()
# Set this user in the session
session.set_user(user.id, session.provider_id)
# Delete now useless session attributes
session.name_identifier = None
session.lasso_anonymous_identity_dump = None
session.provider_id = None
return redirect('%s/admin/' % get_request().environ['SCRIPT_NAME'])

View File

@ -0,0 +1,414 @@
import os
import sys
import urlparse
try:
import lasso
except ImportError:
print >> sys.stderr, 'Missing Lasso module, SAMLv2 support disabled'
from quixote import get_publisher, get_request, get_response, get_session, get_session_manager, redirect
from qommon.liberty import SOAPException, soap_call
from qommon.saml2 import Saml2Directory
from qommon import template
from qommon import get_logger
import misc
from users import User
from hosts import Host
from federations import Federation
import site_authentication
class Saml2(Saml2Directory):
_q_exports = Saml2Directory._q_exports + ['local_auth']
def login(self):
return self.perform_login()
def perform_login(self, idp = None):
server = misc.get_lasso_server(protocol = 'saml2')
if not server:
return template.error_page(_('SAML 2.0 support not yet configured.'))
login = lasso.Login(server)
login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
login.request.nameIDPolicy.allowCreate = True
login.request.forceAuthn = False
login.request.isPassive = False
login.request.consent = 'urn:oasis:names:tc:SAML:2.0:consent:current-implicit'
login.buildAuthnRequestMsg()
return redirect(login.msgUrl)
def singleSignOnArtifact(self):
server = misc.get_lasso_server(protocol = 'saml2')
if not server:
return template.error_page(_('SAML 2.0 support not yet configured.'))
login = lasso.Login(server)
request = get_request()
try:
login.initRequest(request.get_query(), lasso.HTTP_METHOD_ARTIFACT_GET)
except lasso.Error, error:
if error[0] == lasso.PROFILE_ERROR_MISSING_ARTIFACT:
return template.error_page(_('Missing SAML Artifact'))
else:
raise
login.buildRequestMsg()
#remote_provider_cfg = get_cfg('idp', {}).get(misc.get_provider_key(login.remoteProviderId))
#client_cert = remote_provider_cfg.get('clientcertificate')
try:
soap_answer = soap_call(login.msgUrl, login.msgBody)
except SOAPException:
return template.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 template.error_page(_('Unknown authentication failure'))
if error[0] == lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
return template.error_page(_('Authentication failure; unknown principal'))
if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
return template.error_page('there was no federation')
raise
return self.sso_after_response(login)
def sso_after_response(self, login):
try:
assertion = login.response.assertion[0]
if assertion.subject.subjectConfirmation.subjectConfirmationData.recipient != \
get_request().get_url():
return template.error_page('SubjectConfirmation Recipient Mismatch')
except:
return template.error_page('SubjectConfirmation Recipient Mismatch')
assertions_dir = os.path.join(get_publisher().app_dir, 'assertions')
if not os.path.exists(assertions_dir):
os.mkdir(assertions_dir)
assertion_fn = os.path.join(assertions_dir, assertion.iD)
if os.path.exists(assertion_fn):
return template.error_page('Assertion replay')
try:
if assertion.subject.subjectConfirmation.method != \
'urn:oasis:names:tc:SAML:2.0:cm:bearer':
return template.error_page('Unknown SubjectConfirmation Method')
except:
return template.error_page('Unknown SubjectConfirmation Method')
try:
audience_ok = False
for audience_restriction in assertion.conditions.audienceRestriction:
if audience_restriction.audience != login.server.providerId:
return template.error_page('Incorrect AudienceRestriction')
audience_ok = True
if not audience_ok:
return template.error_page('Incorrect AudienceRestriction')
except:
return template.error_page('Incorrect AudienceRestriction')
# try:
# current_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
# not_before = assertion.subject.subjectConfirmation.subjectConfirmationData.notBefore
# not_on_or_after = assertion.subject.subjectConfirmation.subjectConfirmationData.notOnOrAfter
# if not_before and current_time < not_before:
# return template.error_page('Assertion received too early')
# if not_on_or_after and current_time > not_on_or_after:
# return template.error_page('Assertion expired')
# except:
# return template.error_page('Error checking Assertion Time')
# TODO: check for unknown conditions
login.acceptSso()
session = get_session()
if login.isSessionDirty:
if login.session:
session.lasso_session_dumps[login.server.providerId] = login.session.dump()
else:
session.lasso_session_dumps[login.server.providerId] = None
if assertion.authnStatement[0].sessionIndex:
session.lasso_session_index = assertion.authnStatement[0].sessionIndex
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, login.server.providerId)
else:
session.name_identifier = login.nameIdentifier.content
session.lasso_anonymous_identity_dump = login.identity.dump()
session.provider_id = login.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, login.server.providerId)
# Check if a federation already exist
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:
# Build response redirection
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(get_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 slo_sp(self, method = None):
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
if method is None:
method = lasso.HTTP_METHOD_REDIRECT
logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
session = get_session()
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())
if method == lasso.HTTP_METHOD_REDIRECT:
return self.slo_sp_redirect(logout)
# Not implemented yet
if method == lasso.HTTP_METHOD_SOAP:
return self.slo_sp_soap(logout)
def slo_sp_redirect(self, logout):
session = get_session()
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(logout.server.providerId)
return redirect(host.get_root_url())
if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
get_session_manager().expire_session(logout.server.providerId)
return redirect(host.get_root_url())
raise
logout.buildRequestMsg()
return redirect(logout.msgUrl)
def singleLogoutReturn(self):
host = Host.get_host_from_url()
if host is None:
return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
session = get_session()
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])
message = get_request().get_query()
return self.slo_return(logout, message)
def slo_return(self, logout, message):
host = Host.get_host_from_url()
session = get_session()
try:
logout.processResponseMsg(message)
except lasso.Error, error:
if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
get_logger().warn('Invalid response')
elif error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
get_logger().warn('Failed to check single logout request signature')
elif error[0] == lasso.LOGOUT_ERROR_REQUEST_DENIED:
get_logger().warn('Request Denied')
elif error[0] == lasso.LOGOUT_ERROR_UNKNOWN_PRINCIPAL:
get_logger().warn('Unknown principal on logout, probably session stopped already on IdP')
# XXX: wouldn't work when logged on two IdP
del session.lasso_session_dumps[logout.server.providerId]
else:
raise
get_session_manager().expire_session(logout.server.providerId)
return redirect(host.get_root_url())
def singleLogoutSOAP(self):
try:
soap_message = self.get_soap_message()
except:
return
response = get_response()
response.set_content_type('text/xml')
request_type = lasso.getRequestTypeFromSoapMsg(soap_message)
if request_type != lasso.REQUEST_TYPE_LOGOUT:
get_logger().warn('SOAP message on single logout url not a slo message')
return
logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
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:
get_logger().info('SLO/SOAP from %s' % logout.remoteProviderId)
break
else:
# no session, build straight failure answer
logout.buildResponseMsg()
return logout.msgBody
return self.slo_idp(logout, session)
def singleLogout(self):
logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
try:
logout.processRequestMsg(get_request().get_query())
except lasso.Error, error:
if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
return template.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
name_identifier = logout.nameIdentifier.content
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)
def slo_idp(self, logout, session):
# 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 session (%s and %s)' % (
logout.nameIdentifier.content, session.name_identifier)
try:
assertion = logout.session.getAssertions(logout.remoteProviderId)[0]
if logout.request.sessionIndex and (
assertion.authnStatement[0].sessionIndex != logout.request.sessionIndex):
logout.setSessionFromDump('<Session />')
except:
pass
try:
logout.validateRequest()
except lasso.Error, error:
if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
pass
elif error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
pass
elif error[0] == lasso.PROFILE_ERROR_MISSING_ASSERTION:
pass
else:
raise
else:
get_session_manager().expire_session(logout.server.providerId)
try:
if not logout.request.sessionIndex:
for session2 in get_session_manager().values():
if name_identifier == session2.name_identifier:
del get_session_manager()[session2.id]
except:
# killing all session failed, ignoring silently
pass
logout.buildResponseMsg()
if logout.msgBody: # soap answer
return logout.msgBody
else:
return redirect(logout.msgUrl)
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.saml2_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

View File

@ -0,0 +1,76 @@
'''Session and SessionManager objects. Configuration variables and utilities'''
from quixote import get_request
import qommon.sessions
from qommon.sessions import Session
from qommon.sessions import StorageSessionManager as SessionManager
from users import User
from hosts import Host
class BasicSession(Session):
'''Session object. Configuration variables and utilities'''
_names = 'sessions'
users = {}
lasso_session_dumps = {}
provider_id = None
def has_info(self):
return self.users or self.lasso_session_dumps or self.provider_id or Session.has_info(self)
is_dirty = has_info
def get_user(self, provider_id=None):
# Defaults to getting Larpe user.
# It allows get_request().user to work in administration interface.
if not provider_id:
user_id = None
host = Host.get_host_from_url()
if not host:
host = Host.select(lambda x: x.name == 'larpe')[0]
if host:
user_id = self.users.get(host.provider_id)
if not user_id:
user_id = self.users.get(host.saml2_provider_id)
else:
user_id = self.users.get(provider_id)
if user_id:
try:
user = User.get(user_id)
except KeyError:
user = User()
# if str(user_id).startswith('anonymous-'):
user.id = user_id
# user.anonymous = True
# if self.name_identifiers.has_key(providerId):
# if not user.name_identifiers.has_key(providerId):
# user.name_identifiers[providerId] = [ self.name_identifiers[providerId] ]
# if self.name_identifiers.has_key(providerId):
# user.name_identifiers[providerId] = [ self.name_identifiers[providerId] ]
# else:
# user.name_identifiers[providerId] = []
# user.lasso_dumps[providerId] = self.lasso_anonymous_identity_dump
return user
return None
def set_user(self, user_id, provider_id):
self.users[provider_id] = user_id
class StorageSessionManager(SessionManager):
'''SessionManager object. Subclass with multi-hosts specific features.'''
def expire_session(self, provider_id=None):
session = get_request().session
if session.id is not None:
if provider_id:
if session.users.has_key(provider_id):
del session.users[provider_id]
if session.lasso_session_dumps.has_key(provider_id):
del session.lasso_session_dumps[provider_id]
session.store()
if not session.users:
SessionManager.expire_session(self)
qommon.sessions.BasicSession = BasicSession

View File

@ -0,0 +1,324 @@
import libxml2
import urllib
import urlparse
import httplib
import re
import os
import socket
import base64
from quixote import get_request, get_response, get_session, redirect, get_publisher
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.errors import ConnectionError, ConfigurationError, LoginError
from qommon.misc import http_post_request, http_get_page
from qommon.template import *
import misc
from users import User
from federations import Federation
class SiteAuthentication:
def __init__(self, host):
self.host = host
def federate(self, username, password, provider_id, cookies, select):
user = get_session().get_user(provider_id)
if user is not None:
Federation(username, password, self.host.id, user.name_identifiers[0], cookies, select).store()
def sso_local_login(self, federation):
status, data = self.local_auth_check_dispatch(
federation.username, federation.password, federation.select_fields)
success, return_content = self.check_auth(status, data)
if success:
session = get_session()
if hasattr(session, 'cookies'):
federation.set_cookies(session.cookies)
federation.store()
return return_content
else:
return redirect('local_auth')
def local_auth [html] (self, first_time=True):
response = get_response()
response.set_content_type('text/html')
if hasattr(get_response(), str('breadcrumb')):
del get_response().breadcrumb
get_response().filter['default_org'] = '%s - %s' % (self.host.label, _('Local authentication'))
get_response().filter['body_class'] = 'login'
form = self.form_local_auth()
form.add_submit('submit', _('Submit'))
#form.add_submit('cancel', _('Cancel'))
# if form.get_widget('cancel').parse():
# return redirect('.')
authentication_failure = None
if form.is_submitted() and not form.has_errors():
try:
return self.submit_local_auth_form(form)
except LoginError:
authentication_failure = _('Authentication failure')
get_logger().info('local auth page : %s' % authentication_failure)
except ConnectionError, err:
authentication_failure = _('Connection failed : %s') % err
get_logger().info('local auth page : %s' % authentication_failure)
except ConfigurationError, err:
authentication_failure = _('This service provider is not fully configured : %s') % err
get_logger().info('local auth page : %s' % authentication_failure)
except Exception, err:
authentication_failure = _('Unknown error : %s' % err)
get_logger().info('local auth page : %s' % authentication_failure)
if authentication_failure:
'<div class="errornotice">%s</div>' % authentication_failure
'<p>'
_('Please type your login and password for this Service Provider.')
_('Your local account will be federated with your Liberty Alliance account.')
'</p>'
form.render()
# Also used in admin/hosts.ptl
def form_local_auth(self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'username', title = _('Username'), required = True,
size = 30)
form.add(PasswordWidget, 'password', title = _('Password'), required = True,
size = 30)
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)
return form
def submit_local_auth_form(self, form):
username = form.get_widget('username').parse()
password = form.get_widget('password').parse()
select = {}
for name, values in self.host.select_fields.iteritems():
if form.get_widget(name):
select[name] = form.get_widget(name).parse()
return self.local_auth_check(username, password, select)
def local_auth_check(self, username, password, select=None):
select = select or {}
status, data = self.local_auth_check_dispatch(username, password, select)
if status == 0:
raise
success, return_content = self.check_auth(status, data)
if success:
if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
provider_id = self.host.saml2_provider_id
else:
provider_id = self.host.provider_id
session = get_session()
if hasattr(session, 'cookies'):
self.federate(username, password, provider_id, session.cookies, select)
else:
self.federate(username, password, provider_id, None, select)
return return_content
raise LoginError()
def local_auth_check_dispatch(self, username, password, select=None):
select = select or {}
if self.host.auth_mode == 'http_basic':
return self.local_auth_check_http_basic(username, password)
elif self.host.auth_mode == 'form' and hasattr(self.host, 'auth_check_url') \
and self.host.auth_check_url is not None:
return self.local_auth_check_post(username, password, select)
else:
raise ConfigurationError('No authentication form was found')
def local_auth_check_post(self, username, password, select=None):
select = select or {}
url = self.host.auth_check_url
# Build request body
if self.host.post_parameters:
body_params = {}
# Login field
if self.host.post_parameters[self.host.login_field_name]['enabled'] is True:
body_params[self.host.login_field_name] = username
# Password field
if self.host.post_parameters[self.host.password_field_name]['enabled'] is True:
body_params[self.host.password_field_name] = password
# Select fields
for name, value in select.iteritems():
if self.host.post_parameters[name]['enabled'] is True:
body_params[name] = value
# Other fields (hidden, submit and custom)
for name, value in self.host.other_fields.iteritems():
if self.host.post_parameters[name]['enabled'] is True:
body_params[name] = self.host.post_parameters[name]['value']
body = urllib.urlencode(body_params)
else:
# XXX: Legacy (to be removed later) Send all parameters for sites configured with a previous version of Larpe
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 name, value in self.host.other_fields.iteritems():
body += '&%s=%s' % (name, value)
# Build request HTTP headers
if self.host.http_headers:
headers = {}
if self.host.http_headers['X-Forwarded-For']['enabled'] is True:
headers['X-Forwarded-For'] = get_request().get_environ('REMOTE_ADDR', '-')
for name, value in self.host.http_headers.iteritems():
if value['enabled'] is True and value['immutable'] is False:
headers[name] = value['value']
else:
# XXX: (to be removed later) Send default headers for sites configured with a previous version of Larpe
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)
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 local_auth_check_http_basic(self, username, password):
url = self.host.auth_form_url
hostname, query = urllib.splithost(url[5:])
conn = httplib.HTTPConnection(hostname)
auth_header = 'Basic %s' % base64.encodestring('%s:%s' % (username, password))
try:
conn.request('GET', query, headers={'Authorization': auth_header})
except socket.gaierror, err:
print err
conn.close()
return 0, None
else:
response = conn.getresponse()
conn.close()
return response.status, response.read()
def check_auth(self, status, data):
success = False
return_content = ''
# If status is 500, fail without checking other criterias
if status // 100 == 5:
success = False
return_content = redirect(self.host.get_return_url())
# For http auth, only check status code
elif self.host.auth_mode == 'http_basic':
# If failed, status code should be 401
if status // 100 == 2 or status // 100 == 3:
success = True
return_content = redirect(self.host.get_return_url())
else:
if self.host.auth_system == 'password':
# If there is a password field, authentication probably failed
regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
if not regexp.findall(data):
success = True
return_content = redirect(self.host.get_return_url())
elif self.host.auth_system == 'status':
match_status = int(self.host.auth_match_status)
if match_status == status:
success = True
return_content = redirect(self.host.get_return_url())
elif self.host.auth_system == 'match_text':
# If the auth_match_text is not matched, it means the authentication is successful
regexp = re.compile(self.host.auth_match_text, re.DOTALL)
if not regexp.findall(data):
success = True
return_content = redirect(self.host.get_return_url())
return success, return_content
def local_logout(self, federation=None, user=None):
if federation is None and user is not None:
federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
if federations:
federation = federations[0]
# Logout request to the site
url = self.host.logout_url
if url is not None and federation is not None and federation.cookies is not None:
try:
http_get_page(url, {'Cookie': federation.cookies})
except:
pass
# Remove cookies from the browser
if hasattr(self.host, 'cookies'):
for cookie in self.host.cookies:
get_response().expire_cookie(cookie, path='/')
def local_defederate(self, session, provider_id):
if session is None:
return
user = session.get_user(provider_id)
if user is not None:
federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
for federation in federations:
self.local_logout(provider_id, federation)
federation.remove_name_identifier(user.name_identifiers[0])
federation.store()
site_authentication_classes = {}
def register_site_authentication_class(klass):
site_authentication_classes[klass.plugin_name] = klass
def guess_site_authentication_class(html_doc):
for name, klass in site_authentication_classes.iteritems():
if klass.auto_detect_site(html_doc):
return klass.plugin_name
return None
def get_site_authentication(host):
if host.site_authentication_plugin is None:
return SiteAuthentication(host)
return site_authentication_classes[host.site_authentication_plugin](host)

View File

@ -0,0 +1,44 @@
'''User object. Configuration variables and utilities'''
from qommon.storage import StorableObject
class User(StorableObject):
'''User object. Configuration variables and utilities'''
_names = 'users'
name = None
email = None
name_identifiers = None
identification_token = None
lasso_dumps = None
is_admin = False
anonymous = False
def __init__(self, name=None):
StorableObject.__init__(self)
self.name = name
self.name_identifiers = []
self.lasso_dumps = []
def migrate(self):
pass
def remove_name_identifier(self, name_identifier):
self.name_identifiers.remove(name_identifier)
if not self.name_identifiers:
self.remove_self()
else:
self.store()
def get_display_name(self):
if self.name:
return self.name
if self.email:
return self.email
return _('Unknown User')
display_name = property(get_display_name)
def __str__(self):
return 'User %s, name : %s, name identifiers : %s, lasso_dumps : %s, token : %s' \
% (self.id, self.name, self.name_identifiers, self.lasso_dumps,
self.identification_token)

25
larpe/tags/release-1.0/larpectl Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/python
import sys
from larpe import ctl
def print_usage():
print 'Usage: larpectl command [...]'
print ''
print 'Commands:'
print ' start start server'
print ' cache_modulesets parse and cache jhbuild module sets'
if len(sys.argv) < 2:
print_usage()
sys.exit(1)
else:
command = sys.argv[1]
if command == 'start':
ctl.start(sys.argv[2:])
elif command == 'cache_modulesets':
ctl.cache_modulesets()
else:
print_usage()

View File

@ -0,0 +1,17 @@
#!/bin/sh
VERSION="0.2.0"
svn export svn://labs.libre-entreprise.org/svnroot/larpe/larpe/tags/release-${VERSION}
cd release-${VERSION}
cvs -d :pserver:anonymous@labs.libre-entreprise.org:/cvsroot/wcs login
cvs -d :pserver:anonymous@labs.libre-entreprise.org:/cvsroot/wcs export -r HEAD -d larpe/qommon wcs/wcs/qommon
cd ..
mv release-${VERSION} larpe-${VERSION}
mv larpe-${VERSION}/debian .
tar czf larpe_${VERSION}.orig.tar.gz larpe-${VERSION}
mv debian larpe-${VERSION}
cd larpe-${VERSION}
debuild
cd ..
rm -rf larpe-${VERSION}

View File

@ -0,0 +1,49 @@
prefix = /usr
POFILES=$(wildcard *.po)
MOFILES=$(POFILES:.po=.mo)
PYFILES=$(shell find -L ../larpe -name '*.py' -or -name '*.ptl')
RM=rm -f
all: $(MOFILES)
install: all
for file in $(MOFILES); do \
lang=`echo $$file | sed 's/\.mo//'`; \
install -d $(DESTDIR)$(prefix)/share/locale/$$lang/LC_MESSAGES/; \
install -m 0644 $$file $(DESTDIR)$(prefix)/share/locale/$$lang/LC_MESSAGES/larpe.mo; \
done
uninstall:
@for file in $(MOFILES); do \
lang=`echo $$file | sed 's/\.mo//'`; \
$(RM) $(DESTDIR)$(prefix)/share/locale/$$lang/LC_MESSAGES/larpe.mo; \
done
clean:
-$(RM) messages.mo $(MOFILES)
larpe.pot: $(PYFILES)
@echo "Rebuilding the pot file"
$(RM) larpe.pot tmp.*.pot
cnt=0;
for file in $(PYFILES); do \
cnt=$$(expr $$cnt + 1); \
bn=$$cnt.`basename $$file`; \
xgettext --keyword=N_ -c -L Python -o tmp.$$bn.pot $$file; \
done
msgcat tmp.*.pot > larpe.pot
$(RM) tmp.*.pot
%.mo: %.po
msgfmt -o $@ $<
%.po: larpe.pot
@echo -n "Merging larpe.pot and $@"
@msgmerge $@ larpe.pot -o $@.new
@if [ "`diff $@ $@.new | grep '[<>]' | wc -l`" -ne 2 ]; then \
mv -f $@.new $@; \
else \
$(RM) $@.new; \
fi
@msgfmt --statistics $@

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,310 @@
# lint Python modules using external checkers.
#
# This is the main checker controlling the other ones and the reports
# generation. It is itself both a raw checker and an astng checker in order
# to:
# * handle message activation / deactivation at the module level
# * handle some basic but necessary stats'data (number of classes, methods...)
#
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=.svn,qommon
# Pickle collected data for later comparisons.
persistent=yes
# Set the cache size for astng objects.
cache-size=500
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable only checker(s) with the given id(s). This option conflicts with the
# disable-checker option
#enable-checker=
# Enable all checker(s) except those with the given id(s). This option
# conflicts with the enable-checker option
#disable-checker=
# Enable all messages in the listed categories.
#enable-msg-cat=
# Disable all messages in the listed categories.
#disable-msg-cat=
# Enable the message(s) with the given id(s).
#enable-msg=
# Disable the message(s) with the given id(s).
disable-msg=C0111,R0904,W0403
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html
output-format=text
# Include message's id in output
include-ids=yes
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells wether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectivly contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (R0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (R0004).
comment=no
# Enable the report(s) with the given id(s).
#enable-report=
# Disable the report(s) with the given id(s).
#disable-report=
# try to find bugs in the code using type inference
#
[TYPECHECK]
# Tells wether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamicaly set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed.
generated-members=REQUEST,acl_users,aq_parent
# checks for
# * unused variables / imports
# * undefined variables
# * redefinition of variable from builtins or from an outer scope
# * use of variable before assigment
#
[VARIABLES]
# Tells wether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching names used for dummy variables (i.e. not used).
dummy-variables-rgx=_|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# checks for :
# * doc strings
# * modules / classes / functions / methods / arguments / variables name
# * number of arguments, local variables, branchs, returns and statements in
# functions, methods
# * required module attributes
# * dangerous default values as arguments
# * redefinition of function / method / class
# * uses of the global statement
#
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# Regular expression which should only match functions or classes name which do
# not require a docstring
no-docstring-rgx=__.*__
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
#const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
const-rgx=[a-z\_][a-z0-9\_]{2,30}$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# checks for sign of poor/misdesign:
# * number of methods, attributes, local variables...
# * size, complexity of functions, methods
#
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branchs=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# checks for :
# * methods without self as first argument
# * overridden methods signature
# * access only to existant members via self
# * attributes not defined in the __init__ method
# * supported interfaces implementation
# * unreachable code
#
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# checks for
# * external modules dependencies
# * relative / wildcard imports
# * cyclic imports
# * uses of deprecated modules
#
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report R0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report R0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report R0402 must
# not be disabled)
int-import-graph=
# checks for :
# * unauthorized constructions
# * strict indentation
# * line length
# * use of <> instead of !=
#
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# checks for similarities and duplicated code. This computation may be
# memory / CPU intensive, so you should disable it if you experiments some
# problems.
#
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# checks for:
# * warning notes in the code like FIXME, XXX
# * PEP 263: source code with non ascii character but no encoding declaration
#
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO

View File

@ -0,0 +1,18 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Larpe</title>
<link rel="stylesheet" type="text/css" href="/css/larpe.css"/>
<style type="text/css">
p#larpe {
margin-top: 30%;
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<p id="larpe">
Larpe
</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

View File

@ -0,0 +1,283 @@
@import url(../larpe-common.css);
html, body {
margin: 0;
background: white url(page-bg.png) repeat-y;
}
div#main-content {
margin-left: 160px;
margin-top: -10px;
margin-right: 20px;
}
div#main-content h1 {
color: #006699;
font-size: 120%;
}
div#main-content h2 {
color: #006699;
font-size: 115%;
}
div#main-content h3 {
color: #006699;
font-size: 108%
}
div#header {
margin: 0;
background: white url(head-bg.png) repeat-x;
height: 58px;
}
ul#menu {
background: transparent url(head-logo.png) no-repeat;
width: 177px;
margin: 0;
padding: 80px 0 0 5px;
}
a {
color: #0066cc;
text-decoration: none;
border-bottom: 1px dotted #ff9900;
}
p.commands a {
border: 0;
}
ul#menu a {
font-weight: bold;
}
ul#menu li.active a {
border-bottom: 1px solid #ff9900;
}
ul#menu li {
font-size: 90%;
margin-bottom: 1em;
max-width: 130px;
}
div#footer {
display: none;
}
ul.user-info {
position: absolute;
margin: 0;
padding: 0;
right: 25px;
top: 13px;
font-size: 70%;
font-weight: bold;
}
ul.user-info li {
display: inline;
padding-left: 10px;
}
/** end of dc2 changes **/
ul.biglist {
margin: 0;
padding: 0;
}
ul.biglist li {
list-style-type: none;
margin: 4px 0;
padding: 0 2px;
border: 1px solid #888;
background: #ffe;
clear: both;
}
ul.biglist li p.details {
display: block;
margin: 0;
color: #555;
font-size: 80%;
}
ul.biglist li p.commands {
float: right;
margin-top: -17px;
}
ul.biglist li p.commands img {
padding-right: 5px;
}
a img {
border: 0;
}
td.time {
text-align: right;
}
ul.biglist li.disabled, ul.biglist li.disabled p.details {
color: #999;
background: #ddd;
}
dl dt {
margin : 0;
padding : 0 0 0 0;
}
dl dd {
margin : 0.3em 0 1.5em 10px;
}
img.theme-icon {
float: right;
margin: -16px 4px 0px 3px;
border: 1px solid #999;
}
div#new-field table {
margin: 0;
padding: 0;
}
div#new-field div.widget {
margin: 0;
padding: 0;
}
div#new-field div.buttons {
margin: 0;
padding: 0;
}
div#new-field div.buttons input {
margin: 0;
padding: 0;
}
div#new-field {
border: 1px solid #888;
background: #ffe;
margin: 2em 0 4px 0;
padding: 0 2px;
}
div#new-field div.widget {
}
div#new-field h3 {
margin: 0;
font-size: 100%;
}
div#new-field br {
display: none;
}
div#new-field p.commands {
float: right;
margin-top: -17px;
margin-right: 3px;
}
div.WorkflowStatusWidget {
border-left: 1px solid black;
}
p#breadcrumb {
background: #e6e6e6;
-moz-border-radius: 6px;
padding: 3px 8px;
font-size: 80%;
border: 1px solid #bfbfbf;
}
/** steps **/
#steps {
height: 32px;
margin-bottom: 1em;
background: #f0f0f0;
color: #aaa;
}
#steps ol {
list-style: none;
padding: 0 20px;
}
#steps li {
display: inline;
padding-right: 1em;
display: block;
float: left;
width: 30%;
list-style: none;
}
#steps ol ul {
display: none;
}
#steps span.marker {
font-size: 26px;
padding: 2px 9px;
font-weight: bold;
color: white;
text-align: center;
background: #ddd;
border: 1px solid #ddd;
-moz-border-radius: 0.7ex;
}
#steps li.current span.marker {
background: #ffa500;
border: 1px solid #ffc400;
}
#steps span.label {
font-size: 90%;
}
#steps li.current span.label {
color: black;
}
#steps ol ul {
display: none;
}
/** logs **/
form#other-log-select {
margin-top: 2em;
padding-top: 1em;
border-top: 1px solid #999;
}
form#other-log-select select {
margin: 0 1em;
}
tr.level-error td {
border: 1px solid #800;
background: red;
}
tr.level-error td.message {
font-weight: bold;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Some files were not shown because too many files have changed in this diff Show More