Tagging the 1.1 release of the Larpe project
git-svn-id: svn+ssh://labs.libre-entreprise.org/svnroot/larpe@494 3ed937ae-f919-0410-9a43-8e6f19e4ba6e
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,63 @@
|
|||
import re
|
||||
import os
|
||||
import pickle
|
||||
|
||||
from larpe import sessions
|
||||
from mod_python import Cookie
|
||||
|
||||
def is_auth_ok(req):
|
||||
""" Test if you are authenticate on the Larpe server """
|
||||
cookies = Cookie.get_cookies(req, Cookie.MarshalCookie, secret='secret007')
|
||||
sessions_dir = os.path.join("%(larpe_dir)s", "sessions")
|
||||
for name, cookie in cookies.iteritems():
|
||||
value = cookie.value.replace('"', '')
|
||||
if "larpe-" in name and value in os.listdir(sessions_dir):
|
||||
try:
|
||||
file = open(os.path.join(sessions_dir, value), "rb")
|
||||
session = pickle.load(file)
|
||||
if not session.users or not session.id:
|
||||
return False
|
||||
return True
|
||||
except Exception, err:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def filter_page(filter, page):
|
||||
r = re.compile(r"""(<a.*?href=["']).*?(["'].*?>D.*?connexion</a>)""")
|
||||
page = r.sub(r"""\1%(logout_url)s\2""", page)
|
||||
return page
|
||||
|
||||
def outputfilter(filter):
|
||||
""" Apache called this function by default """
|
||||
if not re.search("^/liberty/.*", filter.req.uri) and not is_auth_ok(filter.req):
|
||||
filter.write('<meta http-equiv="refresh" content="0; url=%(login_url)s" />')
|
||||
filter.close()
|
||||
return
|
||||
|
||||
if filter.req.content_type is not None:
|
||||
is_html = re.search('text/html', filter.req.content_type)
|
||||
if filter.req.content_type is None or not is_html:
|
||||
filter.pass_on()
|
||||
else:
|
||||
if not hasattr(filter.req, 'temp_doc'):
|
||||
# Create a new attribute to hold the document
|
||||
filter.req.temp_doc = []
|
||||
# If content-length ended up wrong, Gecko browsers truncated data
|
||||
if 'Content-Length' in filter.req.headers_out:
|
||||
del filter.req.headers_out['Content-Length']
|
||||
|
||||
temp_doc = filter.req.temp_doc
|
||||
s = filter.read()
|
||||
# Could get '' at any point, but only get None at end
|
||||
while s:
|
||||
temp_doc.append(s)
|
||||
s = filter.read()
|
||||
|
||||
# The end
|
||||
if s is None:
|
||||
page = ''.join(temp_doc)
|
||||
page = filter_page(filter, page)
|
||||
filter.write(page)
|
||||
filter.close()
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import re
|
||||
|
||||
def filter_page(filter, page):
|
||||
current_form = re.compile('<form [^>]*?action="%(auth_form_action)s".*?>.*?</form>', re.DOTALL)
|
||||
page = current_form.sub('<form method="post" action="/liberty/%(name)s/login"><input type="submit" value="Connexion" /></form>', page)
|
||||
return page
|
||||
|
||||
def outputfilter(filter):
|
||||
# Only filter html code
|
||||
if filter.req.content_type is not None:
|
||||
is_html = re.search('text/html', filter.req.content_type)
|
||||
if filter.req.content_type is None or not is_html:
|
||||
filter.pass_on()
|
||||
else:
|
||||
if not hasattr(filter.req, 'temp_doc'):
|
||||
# Create a new attribute to hold the document
|
||||
filter.req.temp_doc = []
|
||||
# If content-length ended up wrong, Gecko browsers truncated data
|
||||
if 'Content-Length' in filter.req.headers_out:
|
||||
del filter.req.headers_out['Content-Length']
|
||||
|
||||
temp_doc = filter.req.temp_doc
|
||||
s = filter.read()
|
||||
# Could get '' at any point, but only get None at end
|
||||
while s:
|
||||
temp_doc.append(s)
|
||||
s = filter.read()
|
||||
|
||||
# The end
|
||||
if s is None:
|
||||
page = ''.join(temp_doc)
|
||||
page = filter_page(filter, page)
|
||||
filter.write(page)
|
||||
filter.close()
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
larpe (1.1-1) unstable; urgency=low
|
||||
|
||||
* New release
|
||||
- Rewrite filter management
|
||||
- Filters are now customisable
|
||||
- Improve Ciril module
|
||||
- Improve plugins management
|
||||
- Support multi filters
|
||||
- Fix SAML 2 logout
|
||||
- Fix site authentification plugins management
|
||||
- Fix sessions management
|
||||
- Code cleaning
|
||||
|
||||
-- Jerome Schneider <jschneider@entrouvert.com> Mon, 19 Jul 2010 11:52:47 +0200
|
||||
|
||||
larpe (1.0-1) unstable; urgency=low
|
||||
|
||||
* New release
|
||||
- SAML 2.0 and ID-FF 1.2 support (authentication and logout)
|
||||
- Form prefilling with ID-WSF 2.0
|
||||
- Configuration assistant (wizard-like) for configuring new sites
|
||||
- Fully tested for several sites with very different behaviours
|
||||
- Plugin system to handle specific behaviour of some sites
|
||||
- Automatic Apache 2 configuration
|
||||
- Automatic creation of Apache python filters to transform authentication
|
||||
boxes on the sites
|
||||
- Support for proxies
|
||||
- Logging and debug options
|
||||
|
||||
-- Damien Laniel <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
|
||||
|
|
@ -0,0 +1 @@
|
|||
5
|
|
@ -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
|
|
@ -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.8.0
|
||||
XS-Python-Version: current
|
||||
|
||||
Package: larpe
|
||||
Architecture: any
|
||||
XB-Python-Version: ${python:Versions}
|
||||
Depends: ${shlibs: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.
|
||||
.
|
|
@ -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'.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
etc/apache2/sites-available
|
||||
etc/larpe
|
||||
usr/sbin
|
||||
var/lib/larpe
|
|
@ -0,0 +1,2 @@
|
|||
README
|
||||
AUTHORS
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
/etc/init.d/apache2 reload
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
2
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/make -f
|
||||
# GNU copyright 1997 to 1999 by Joey Hess.
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
PYTHON=/usr/bin/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_shlibdeps
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
|
||||
binary: binary-indep binary-arch
|
||||
.PHONY: build clean binary-indep binary-arch binary install
|
|
@ -0,0 +1,8 @@
|
|||
all:
|
||||
$(MAKE) -C en
|
||||
|
||||
clean:
|
||||
$(MAKE) -C en clean
|
||||
|
||||
.PHONY: clean
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
@ -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/
|
|
@ -0,0 +1,5 @@
|
|||
#! /bin/sh
|
||||
|
||||
size=$(identify $1 | cut -d ' ' -f 3)
|
||||
composite $1 -size $(identify $1 | cut -d ' ' -f3) xc:white $2
|
||||
|
|
@ -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
|
|
@ -0,0 +1,12 @@
|
|||
.svn
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pye
|
||||
*.ptle
|
||||
*.swp
|
||||
debian.sarge
|
||||
make_debian_package.sh
|
||||
build
|
||||
dist
|
||||
tests
|
||||
larpe/filter
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
'''Default global variables'''
|
||||
|
||||
APP_DIR = '/var/lib/larpe'
|
||||
DATA_DIR = '/usr/share/larpe'
|
||||
ERROR_LOG = None #'/var/log/larpe.log'
|
||||
WEB_ROOT = 'larpe'
|
||||
APACHE_MAIN_VHOST = '/etc/apache2/sites-available/apache2-vhost-larpe'
|
||||
APACHE_VHOST_COMMON = '/etc/larpe/apache2-vhost-larpe-common'
|
||||
APACHE_RELOAD = '/usr/sbin/larpe-reload-apache2'
|
|
@ -0,0 +1,21 @@
|
|||
'''Setup path and load Lasso library'''
|
||||
try:
|
||||
from quixote.ptl import compile_package
|
||||
compile_package(__path__)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
import qommon
|
||||
|
||||
try:
|
||||
import lasso
|
||||
except ImportError:
|
||||
lasso = None
|
||||
|
||||
if lasso and not hasattr(lasso, 'SAML2_SUPPORT'):
|
||||
lasso.SAML2_SUPPORT = False
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
try:
|
||||
from quixote.ptl import compile_package
|
||||
compile_package(__path__)
|
||||
except ImportError:
|
||||
pass
|
||||
from root import RootDirectory
|
|
@ -0,0 +1,300 @@
|
|||
import os
|
||||
import re
|
||||
import urllib
|
||||
import base64
|
||||
|
||||
from quixote import get_publisher, get_request
|
||||
|
||||
from qommon import get_cfg
|
||||
|
||||
from larpe.hosts import Host
|
||||
from larpe.Defaults import APP_DIR, APACHE_MAIN_VHOST, APACHE_VHOST_COMMON, APACHE_RELOAD
|
||||
|
||||
def write_apache2_vhosts():
|
||||
hosts = Host.select(lambda x: x.name != 'larpe')
|
||||
hosts.sort()
|
||||
vhosts_dir = os.path.join(APP_DIR, 'vhosts.d')
|
||||
vhost_locations_dir = os.path.join(APP_DIR, 'vhost-locations.d')
|
||||
vhosts_dir_disabled = os.path.join(APP_DIR, 'vhosts.d.disabled')
|
||||
vhost_locations_dir_disabled = os.path.join(APP_DIR, 'vhost-locations.d.disabled')
|
||||
vhost_file_name = get_request().get_server().split(':')[0]
|
||||
vhost_file = None
|
||||
reversed_hostname = ''
|
||||
vhost = None
|
||||
|
||||
if get_publisher().cfg.get(str('allow_config_generation'), True):
|
||||
vhost_file = open(os.path.join(vhosts_dir, vhost_file_name), 'w')
|
||||
locations_file = open(os.path.join(vhost_locations_dir, vhost_file_name), 'w')
|
||||
else:
|
||||
vhost_file = open(os.path.join(vhosts_dir_disabled, vhost_file_name), 'w')
|
||||
locations_file = open(os.path.join(vhost_locations_dir_disabled, vhost_file_name), 'w')
|
||||
try:
|
||||
main_vhost = open(APACHE_MAIN_VHOST, 'r')
|
||||
file_content = main_vhost.read()
|
||||
regexp = re.compile('<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 self.host.apache_output_python_filters and \
|
||||
self.host.apache_python_paths:
|
||||
python_path = 'PythonPath "sys.path'
|
||||
for path in self.host.apache_python_paths:
|
||||
python_path += "+['%s']" % path
|
||||
python_path += '"'
|
||||
return python_path
|
||||
else:
|
||||
return None
|
||||
python_path = property(get_python_path)
|
||||
|
||||
def get_output_filters(self):
|
||||
python_filters = ''
|
||||
output_filters = []
|
||||
if self.host.apache_output_python_filters:
|
||||
i = 0
|
||||
for filter_file in self.host.apache_output_python_filters:
|
||||
filter_name = 'filter%d' % i
|
||||
python_filters += 'PythonOutputFilter %s %s\n\t\t' % (filter_file, filter_name)
|
||||
output_filters.append(filter_name)
|
||||
i += 1
|
||||
if self.host.apache_output_filters:
|
||||
for output_filter in self.host.apache_output_filters:
|
||||
output_filters.append(output_filter)
|
||||
if output_filters:
|
||||
return python_filters + 'SetOutputFilter ' + ';'.join(output_filters)
|
||||
else:
|
||||
return None
|
||||
output_filters = property(get_output_filters)
|
||||
|
||||
def get_old_auth_url(self):
|
||||
old_auth_url = None
|
||||
if self.host.initiate_sso_url:
|
||||
old_auth_url = self.host.initiate_sso_url
|
||||
elif self.host.auth_url is not None:
|
||||
if self.host.auth_url.startswith('http://'):
|
||||
chars_to_skip = 5
|
||||
else:
|
||||
chars_to_skip = 6
|
||||
regexp = re.compile(self.host.orig_site[chars_to_skip:])
|
||||
old_auth_url_short = regexp.sub('', self.host.auth_url[chars_to_skip:])
|
||||
if old_auth_url_short.startswith('/'):
|
||||
old_auth_url = old_auth_url_short
|
||||
else:
|
||||
old_auth_url = '/' + old_auth_url_short
|
||||
if old_auth_url:
|
||||
old_auth_url = apache_escape_chars(old_auth_url)
|
||||
return old_auth_url
|
||||
old_auth_url = property(get_old_auth_url)
|
||||
|
||||
def get_new_auth_url(self):
|
||||
if not hasattr(self.host, 'base_url'):
|
||||
return None
|
||||
base_url_tokens = self.host.base_url.split('/')
|
||||
base_url_tokens[-1] = 'login'
|
||||
return '/'.join(base_url_tokens)
|
||||
new_auth_url = property(get_new_auth_url)
|
||||
|
||||
def get_old_logout_url(self):
|
||||
old_logout_url = None
|
||||
if self.host.logout_url is not None:
|
||||
if self.host.logout_url.startswith('http://'):
|
||||
chars_to_skip = 5
|
||||
else:
|
||||
chars_to_skip = 6
|
||||
regexp = re.compile(self.host.orig_site[chars_to_skip:])
|
||||
old_logout_url_short = regexp.sub('', self.host.logout_url[chars_to_skip:])
|
||||
if old_logout_url_short.startswith('/'):
|
||||
old_logout_url = old_logout_url_short
|
||||
else:
|
||||
old_logout_url = '/' + old_logout_url_short
|
||||
old_logout_url = apache_escape_chars(old_logout_url)
|
||||
return old_logout_url
|
||||
old_logout_url = property(get_old_logout_url)
|
||||
|
||||
def get_new_logout_url(self):
|
||||
if not hasattr(self.host, 'base_url'):
|
||||
return None
|
||||
base_url_tokens = self.host.base_url.split('/')
|
||||
base_url_tokens[-1] = 'logout'
|
||||
return '/'.join(base_url_tokens)
|
||||
new_logout_url = property(get_new_logout_url)
|
||||
|
||||
def get_orig_site_url_and_dir(self):
|
||||
# Split url
|
||||
if self.host.orig_site.startswith('http://'):
|
||||
orig_host, orig_query = urllib.splithost(self.host.orig_site[5:])
|
||||
else:
|
||||
orig_host, orig_query = urllib.splithost(self.host.orig_site[6:])
|
||||
# Add a trailing slash if necessary
|
||||
if self.host.orig_site.endswith('/'):
|
||||
orig_url = self.host.orig_site
|
||||
orig_dir = orig_query
|
||||
else:
|
||||
orig_url = self.host.orig_site + '/'
|
||||
orig_dir = orig_query + '/'
|
||||
return orig_url, orig_dir
|
||||
|
||||
def get_orig_url(self):
|
||||
orig_url, orig_dir = self.get_orig_site_url_and_dir()
|
||||
return orig_url
|
||||
orig_url = property(get_orig_url)
|
||||
|
||||
def get_orig_dir(self):
|
||||
orig_url, orig_dir = self.get_orig_site_url_and_dir()
|
||||
return orig_dir
|
||||
orig_dir = property(get_orig_dir)
|
||||
|
||||
def get_cfg(self):
|
||||
return { 'reversed_directory': self.reversed_directory,
|
||||
'old_auth_url': self.old_auth_url,
|
||||
'new_auth_url': self.new_auth_url,
|
||||
'old_logout_url': self.old_logout_url,
|
||||
'new_logout_url': self.new_logout_url,
|
||||
'orig_url': self.orig_url,
|
||||
'orig_dir': self.orig_dir }
|
||||
cfg = property(get_cfg)
|
||||
|
||||
def write(self, conf_file):
|
||||
conf_lines = []
|
||||
# Start Location
|
||||
conf_lines.append('\n\t<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)
|
||||
# Redirect rules
|
||||
# Redirect old authentication url to the new one
|
||||
if self.old_auth_url is not None and self.host.auth_form_places == 'form_once':
|
||||
conf_lines.append(
|
||||
'RedirectMatch %(old_auth_url)s %(new_auth_url)s' % self.cfg)
|
||||
# Redirect old logout url to the new one
|
||||
if self.old_logout_url is not None:
|
||||
conf_lines.append(
|
||||
'RedirectMatch %(old_logout_url)s %(new_logout_url)s' % self.cfg)
|
||||
# Redirect the home page to the login page
|
||||
if self.host.redirect_root_to_login is True:
|
||||
conf_lines.append('RedirectMatch ^/$ %(new_auth_url)s' % self.cfg)
|
||||
# Convert urls in http headers to/from the new domain
|
||||
conf_lines.append('ProxyPass %(orig_url)s' % self.cfg)
|
||||
conf_lines.append('ProxyPassReverse %(orig_url)s' % self.cfg)
|
||||
# Convert urls in html pages to/from the new domain
|
||||
conf_lines.append('ProxyHTMLURLMap %(orig_dir)s %(reversed_directory)s' % self.cfg)
|
||||
conf_lines.append('ProxyHTMLURLMap %(orig_url)s %(reversed_directory)s' % self.cfg)
|
||||
# Write it all and close the Location
|
||||
conf_file.write('\n\t\t'.join(conf_lines))
|
||||
conf_file.write('\n\t</Location>\n')
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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])
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
|
@ -0,0 +1,276 @@
|
|||
import random
|
||||
|
||||
import lasso
|
||||
|
||||
from quixote import get_request, get_session, redirect, get_publisher
|
||||
from quixote.directory import Directory
|
||||
|
||||
from qommon.admin.menu import html_top, error_page, command_icon
|
||||
from qommon.errors import EmailError
|
||||
from qommon.form import *
|
||||
from qommon import emails
|
||||
|
||||
from larpe import errors
|
||||
from larpe import misc
|
||||
from larpe.users import User
|
||||
from larpe.hosts import Host
|
||||
|
||||
class UserUI:
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
|
||||
def form_new(self):
|
||||
form = Form(enctype="multipart/form-data")
|
||||
form.add(StringWidget, "name", title = _('User Name'), required = True, size=30)
|
||||
form.add(StringWidget, "email", title = _('Email'), required = False, size=30)
|
||||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
return form
|
||||
|
||||
def form_edit(self):
|
||||
form = Form(enctype="multipart/form-data")
|
||||
form.add(StringWidget, "name", title = _('User Name'), required = True, size=30,
|
||||
value = self.user.name)
|
||||
form.add(StringWidget, "email", title = _('Email'), required = False, size=30,
|
||||
value = self.user.email)
|
||||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
return form
|
||||
|
||||
def submit_form(self, form):
|
||||
if not self.user:
|
||||
self.user = User()
|
||||
for f in ('name', 'email'):
|
||||
widget = form.get_widget(f)
|
||||
if widget:
|
||||
setattr(self.user, f, widget.parse())
|
||||
self.user.is_admin = True
|
||||
self.user.store()
|
||||
|
||||
|
||||
class UserPage(Directory):
|
||||
_q_exports = ['', 'edit', 'delete', 'token']
|
||||
|
||||
def __init__(self, component):
|
||||
self.user = User.get(component)
|
||||
self.user_ui = UserUI(self.user)
|
||||
get_response().breadcrumb.append((component + '/', self.user.name))
|
||||
|
||||
def _q_index [html] (self):
|
||||
html_top('users', '%s - %s' % (_('User'), self.user.name))
|
||||
'<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 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()
|
|
@ -0,0 +1,2 @@
|
|||
from start import start
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import socket
|
||||
import sys
|
||||
import quixote.server.simple_server
|
||||
|
||||
from qommon.scgi_server import run
|
||||
|
||||
import publisher
|
||||
|
||||
def start(args):
|
||||
run_function = run
|
||||
run_kwargs = {
|
||||
'port': 3007,
|
||||
'script_name': ''
|
||||
}
|
||||
http = 0
|
||||
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i] == '--port':
|
||||
run_kwargs['port'] = int(args[i+1])
|
||||
i += 1
|
||||
elif args[i] == '--silent':
|
||||
sys.stdout = open('/dev/null', 'w')
|
||||
sys.stderr = open('/dev/null', 'w')
|
||||
elif args[i] == '--script-name':
|
||||
run_kwargs['script_name'] = args[i+1]
|
||||
i += 1
|
||||
elif args[i] == '--app-dir':
|
||||
publisher.LarpePublisher.APP_DIR = args[i+1]
|
||||
i += 1
|
||||
elif args[i] == '--data-dir':
|
||||
publisher.LarpePublisher.DATA_DIR = args[i+1]
|
||||
i += 1
|
||||
elif args[i] == '--http':
|
||||
http = 1
|
||||
i += 1
|
||||
|
||||
if http == 1:
|
||||
run_function = quixote.server.simple_server.run
|
||||
if run_kwargs['script_name']:
|
||||
print "--http option is incompatible with --script-name"
|
||||
del run_kwargs['script_name']
|
||||
try:
|
||||
run_function(publisher.LarpePublisher.create_publisher, **run_kwargs)
|
||||
except socket.error, err:
|
||||
if err[0] == 98:
|
||||
print >> sys.stderr, 'address already in use'
|
||||
sys.exit(1)
|
||||
raise
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from quixote import get_session, get_request, redirect
|
||||
|
||||
from qommon.errors import *
|
||||
|
||||
class AccessUnauthorizedError(AccessError):
|
||||
def render [html] (self):
|
||||
session = get_session()
|
||||
request = get_request()
|
||||
query = request.get_query()
|
||||
session.after_url = request.get_url()
|
||||
if query:
|
||||
session.after_url += '?' + query
|
||||
session.after_url = str(session.after_url)
|
||||
login_url = '%s/liberty/larpe/login' % request.environ['SCRIPT_NAME']
|
||||
redirect(login_url)
|
|
@ -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)
|
|
@ -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 = {}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from larpe.plugins.site_authentication_plugins import SiteAuthenticationPlugins
|
||||
|
||||
site_authentication_plugins = SiteAuthenticationPlugins()
|
|
@ -0,0 +1,142 @@
|
|||
import re
|
||||
import urllib
|
||||
|
||||
from quixote import get_request, get_response, get_session
|
||||
|
||||
from qommon.misc import http_post_request
|
||||
from qommon.errors import ConnectionError
|
||||
from qommon import get_logger
|
||||
|
||||
from larpe.qommon.misc import http_get_page
|
||||
from larpe.plugins import site_authentication_plugins
|
||||
from larpe.site_authentication import SiteAuthentication
|
||||
|
||||
class AgirheSiteAuthentication(SiteAuthentication):
|
||||
plugin_name = 'agirhe'
|
||||
|
||||
def auto_detect_site(cls, html_doc):
|
||||
if re.search(
|
||||
"""<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_plugins.register(AgirheSiteAuthentication)
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
import re
|
||||
|
||||
from quixote import redirect
|
||||
|
||||
from larpe.plugins import site_authentication_plugins
|
||||
from larpe.site_authentication import SiteAuthentication
|
||||
|
||||
class CirilSiteAuthentication(SiteAuthentication):
|
||||
|
||||
plugin_name = 'ciril'
|
||||
output_filters = ['output_ciril_net_rh']
|
||||
|
||||
def auto_detect_site(cls, html_doc):
|
||||
if re.search(
|
||||
"""<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_plugins.register(CirilSiteAuthentication)
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import re
|
||||
|
||||
from quixote import get_request, get_response, get_session
|
||||
|
||||
from qommon.misc import http_post_request
|
||||
from qommon import get_logger
|
||||
|
||||
from larpe.plugins import site_authentication_plugins
|
||||
from larpe.site_authentication import SiteAuthentication
|
||||
|
||||
class ConcertoSiteAuthentication(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_plugins.register(ConcertoSiteAuthentication)
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import re
|
||||
import urlparse
|
||||
|
||||
from quixote import get_request, get_response, get_session
|
||||
|
||||
from qommon.misc import http_post_request, http_get_page
|
||||
from qommon import get_logger
|
||||
|
||||
from larpe.plugins import site_authentication_plugins
|
||||
from larpe.site_authentication import SiteAuthentication
|
||||
|
||||
class EgroupwareSiteAuthentication(SiteAuthentication):
|
||||
plugin_name = 'egroupware'
|
||||
|
||||
def auto_detect_site(cls, html_doc):
|
||||
if re.search("""<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_plugins.register(EgroupwareSiteAuthentication)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import re
|
||||
|
||||
from quixote import get_response, redirect
|
||||
from quixote.html import htmltext
|
||||
|
||||
from larpe.plugins import site_authentication_plugins
|
||||
from larpe.site_authentication import SiteAuthentication
|
||||
|
||||
class SympaSiteAuthentication(SiteAuthentication):
|
||||
plugin_name = 'sympa'
|
||||
|
||||
def auto_detect_site(cls, html_doc):
|
||||
if re.search("""<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_plugins.register(SympaSiteAuthentication)
|
|
@ -0,0 +1,35 @@
|
|||
import ipdb
|
||||
|
||||
class SiteAuthenticationPlugins:
|
||||
""" This class manages the plugins for site authentification """
|
||||
|
||||
def __init__(self):
|
||||
self.site_authentication_classes = dict()
|
||||
|
||||
def register(self, klass):
|
||||
""" Register a custom SiteAuthentification instance """
|
||||
self.site_authentication_classes[klass.plugin_name] = klass
|
||||
|
||||
def get(self, plugin_name):
|
||||
""" Return a custom SiteAuthentification instance """
|
||||
if self.site_authentication_classes.has_key(plugin_name):
|
||||
return self.site_authentication_classes[plugin_name]
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_plugins_name(self):
|
||||
plugins_name = list()
|
||||
for plugin_name in self.site_authentication_classes.iterkeys():
|
||||
plugins_name.append(plugin_name)
|
||||
return plugins_name
|
||||
|
||||
def auto_detect(self, html_doc):
|
||||
"""
|
||||
Try to find automatically the right plugin name
|
||||
Return the plugin name or None
|
||||
"""
|
||||
for name, klass in self.site_authentication_classes.iteritems():
|
||||
if klass.auto_detect_site(html_doc):
|
||||
return klass.plugin_name
|
||||
return None
|
||||
|
|
@ -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)
|
|
@ -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'])
|
||||
|
|
@ -0,0 +1,448 @@
|
|||
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):
|
||||
providerId = login.server.providerId
|
||||
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 != 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[providerId] = login.session.dump()
|
||||
session.lasso_session_indexes[providerId] = assertion.authnStatement[0].sessionIndex
|
||||
session.lasso_session_name_identifiers[providerId] = login.nameIdentifier.content
|
||||
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, ignore_errors = True))
|
||||
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')
|
||||
else:
|
||||
get_logger().error('Unknown Lasso exception on logout return: ' + repr(error))
|
||||
except Exception, exception:
|
||||
get_logger().error('Unknown exception on logout return: ' + repr(exception))
|
||||
|
||||
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'))
|
||||
providerId = logout.server.providerId
|
||||
logout.processRequestMsg(soap_message)
|
||||
name_identifier = logout.nameIdentifier.content
|
||||
# find one session matching the name identifier, and eventually the request
|
||||
for session in get_session_manager().values():
|
||||
session_index = session.lasso_session_indexes.get(providerId)
|
||||
name_identifier = session.lasso_session_name_identifiers.get(providerId)
|
||||
request_name_identifier = logout.nameIdentifier.content
|
||||
request_session_index = logout.request.sessionIndex
|
||||
if request_name_identifier == name_identifier and \
|
||||
(not session_index or request_session_index == session_index) \
|
||||
and session.lasso_session_dumps.get(providerId):
|
||||
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
|
||||
# find a matching
|
||||
for session in get_session_manager().values():
|
||||
session_index = session.lasso_session_indexes.get(providerId)
|
||||
name_identifier = session.lasso_session_name_identifiers.get(providerId)
|
||||
request_name_identifier = logout.nameIdentifier.content
|
||||
request_session_index = logout.request.sessionIndex
|
||||
if request_name_identifier == name_identifier and \
|
||||
(not session_index or request_session_index == session_index) \
|
||||
and session.lasso_session_dump.get(providerId):
|
||||
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 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
|
||||
elif error[0] == lasso.SERVER_ERROR_PROVIDER_NOT_FOUND:
|
||||
pass
|
||||
elif error[0] == lasso.NAME_IDENTIFIER_NOT_FOUND:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
providerId = logout.server.providerId
|
||||
session_index = logout.request.sessionIndex
|
||||
name_identifier = logout.nameIdentifier.content
|
||||
# Remove reference to local authentication on this SP in the session
|
||||
# if a user is present, try a local logout
|
||||
for session2 in get_session_manager().values():
|
||||
if session2.lasso_session_name_identifiers.get(providerId) == name_identifier \
|
||||
and ( not session_index
|
||||
or session2.lasso_session_indexes.get(providerId) == session_index):
|
||||
if session2.users.has_key(providerId):
|
||||
# local logout
|
||||
site_auth = site_authentication.get_site_authentication(Host.get_host_from_url())
|
||||
site_auth.local_logout(user=session2.get_user(providerId),
|
||||
cookies=getattr(session2,'cookies', None))
|
||||
del session2.users[providerId]
|
||||
if session2.lasso_session_dumps.has_key(providerId):
|
||||
del session2.lasso_session_dumps[providerId]
|
||||
if session2.lasso_session_indexes.has_key(providerId):
|
||||
del session2.lasso_session_indexes[providerId]
|
||||
if session2.lasso_session_name_identifiers.has_key(providerId):
|
||||
del session2.lasso_session_name_identifiers[providerId]
|
||||
session2.store()
|
||||
get_session_manager().expire_session(logout.server.providerId)
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
'''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'
|
||||
|
||||
def __init__(self, id):
|
||||
self.users = {}
|
||||
self.lasso_session_dumps = {}
|
||||
self.lasso_session_indexes = {}
|
||||
self.lasso_session_name_identifiers = {}
|
||||
self.provider_id = None
|
||||
Session.__init__(self, id)
|
||||
|
||||
# lasso_session_indexes newly introduced
|
||||
def __setstate__(self, dict):
|
||||
self.lasso_session_indexes = {}
|
||||
self.lasso_session_name_identifiers = {}
|
||||
self.__dict__.update(dict)
|
||||
|
||||
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_list = Host.select(lambda x: x.name == 'larpe')
|
||||
if host_list:
|
||||
host = host_list[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]
|
||||
if session.lasso_session_indexes.has_key(provider_id):
|
||||
del session.lasso_session_indexes[provider_id]
|
||||
if session.lasso_session_name_identifiers.has_key(provider_id):
|
||||
del session.lasso_session_name_identifiers[provider_id]
|
||||
session.store()
|
||||
if not session.users:
|
||||
SessionManager.expire_session(self)
|
||||
|
||||
qommon.sessions.BasicSession = BasicSession
|
|
@ -0,0 +1,321 @@
|
|||
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 *
|
||||
|
||||
from larpe.plugins import site_authentication_plugins
|
||||
|
||||
import misc
|
||||
from users import User
|
||||
from federations import Federation
|
||||
|
||||
class SiteAuthentication:
|
||||
|
||||
output_filters = []
|
||||
|
||||
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, cookies=None):
|
||||
if cookies is None and federation is None and user is not None:
|
||||
federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
|
||||
if federations:
|
||||
cookies = federations[0].cookies
|
||||
|
||||
# Logout request to the site
|
||||
url = self.host.logout_url
|
||||
if url is not None and cookies is not None:
|
||||
try:
|
||||
http_get_page(url, {'Cookie': cookies})
|
||||
except ConnectionError, err:
|
||||
get_logger().warning(_("%s logout failed") % url)
|
||||
get_logger().debug(err)
|
||||
|
||||
# Remove cookies from the browser
|
||||
# TODO: this should be removed because this only works
|
||||
# with a 'direct' logout
|
||||
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()
|
||||
|
||||
def get_site_authentication(host):
|
||||
if host.site_authentication_plugin is None:
|
||||
return SiteAuthentication(host)
|
||||
return site_authentication_plugins.get(host.site_authentication_plugin)(host)
|
||||
|
|
@ -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)
|
|
@ -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()
|
|
@ -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 $@
|
|
@ -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
|
|
@ -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>
|
After Width: | Height: | Size: 633 B |
After Width: | Height: | Size: 886 B |
|
@ -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;
|
||||
}
|
||||
|
After Width: | Height: | Size: 357 B |
After Width: | Height: | Size: 830 B |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 225 B |
After Width: | Height: | Size: 296 B |
After Width: | Height: | Size: 200 B |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 200 B |
After Width: | Height: | Size: 87 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 160 B |
After Width: | Height: | Size: 117 B |
After Width: | Height: | Size: 102 B |
After Width: | Height: | Size: 216 B |