Projet

Général

Profil

« Précédent | Suivant » 

Révision 009bafa7

Ajouté par Jérôme Schneider il y a environ 13 ans

  • ID 009bafa7733f92a5a7d6bd63788dfaf75269dec3

Cleanning repository

Voir les différences:

AUTHORS
1
Damien Laniel <dlaniel@entrouvert.com>
2

  
3
Artwork and administrave interface design taken from DotClear, version 1.2 and
4
2.0, released under the GNU General Public License; and GTK+, version 2.8,
5
released under the GNU Lesser General Public License.
COPYING
1
		    GNU GENERAL PUBLIC LICENSE
2
		       Version 2, June 1991
3

  
4
 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
 Everyone is permitted to copy and distribute verbatim copies
7
 of this license document, but changing it is not allowed.
8

  
9
			    Preamble
10

  
11
  The licenses for most software are designed to take away your
12
freedom to share and change it.  By contrast, the GNU General Public
13
License is intended to guarantee your freedom to share and change free
14
software--to make sure the software is free for all its users.  This
15
General Public License applies to most of the Free Software
16
Foundation's software and to any other program whose authors commit to
17
using it.  (Some other Free Software Foundation software is covered by
18
the GNU Lesser General Public License instead.)  You can apply it to
19
your programs, too.
20

  
21
  When we speak of free software, we are referring to freedom, not
22
price.  Our General Public Licenses are designed to make sure that you
23
have the freedom to distribute copies of free software (and charge for
24
this service if you wish), that you receive source code or can get it
25
if you want it, that you can change the software or use pieces of it
26
in new free programs; and that you know you can do these things.
27

  
28
  To protect your rights, we need to make restrictions that forbid
29
anyone to deny you these rights or to ask you to surrender the rights.
30
These restrictions translate to certain responsibilities for you if you
31
distribute copies of the software, or if you modify it.
32

  
33
  For example, if you distribute copies of such a program, whether
34
gratis or for a fee, you must give the recipients all the rights that
35
you have.  You must make sure that they, too, receive or can get the
36
source code.  And you must show them these terms so they know their
37
rights.
38

  
39
  We protect your rights with two steps: (1) copyright the software, and
40
(2) offer you this license which gives you legal permission to copy,
41
distribute and/or modify the software.
42

  
43
  Also, for each author's protection and ours, we want to make certain
44
that everyone understands that there is no warranty for this free
45
software.  If the software is modified by someone else and passed on, we
46
want its recipients to know that what they have is not the original, so
47
that any problems introduced by others will not reflect on the original
48
authors' reputations.
49

  
50
  Finally, any free program is threatened constantly by software
51
patents.  We wish to avoid the danger that redistributors of a free
52
program will individually obtain patent licenses, in effect making the
53
program proprietary.  To prevent this, we have made it clear that any
54
patent must be licensed for everyone's free use or not licensed at all.
55

  
56
  The precise terms and conditions for copying, distribution and
57
modification follow.
58

  
59
		    GNU GENERAL PUBLIC LICENSE
60
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61

  
62
  0. This License applies to any program or other work which contains
63
a notice placed by the copyright holder saying it may be distributed
64
under the terms of this General Public License.  The "Program", below,
65
refers to any such program or work, and a "work based on the Program"
66
means either the Program or any derivative work under copyright law:
67
that is to say, a work containing the Program or a portion of it,
68
either verbatim or with modifications and/or translated into another
69
language.  (Hereinafter, translation is included without limitation in
70
the term "modification".)  Each licensee is addressed as "you".
71

  
72
Activities other than copying, distribution and modification are not
73
covered by this License; they are outside its scope.  The act of
74
running the Program is not restricted, and the output from the Program
75
is covered only if its contents constitute a work based on the
76
Program (independent of having been made by running the Program).
77
Whether that is true depends on what the Program does.
78

  
79
  1. You may copy and distribute verbatim copies of the Program's
80
source code as you receive it, in any medium, provided that you
81
conspicuously and appropriately publish on each copy an appropriate
82
copyright notice and disclaimer of warranty; keep intact all the
83
notices that refer to this License and to the absence of any warranty;
84
and give any other recipients of the Program a copy of this License
85
along with the Program.
86

  
87
You may charge a fee for the physical act of transferring a copy, and
88
you may at your option offer warranty protection in exchange for a fee.
89

  
90
  2. You may modify your copy or copies of the Program or any portion
91
of it, thus forming a work based on the Program, and copy and
92
distribute such modifications or work under the terms of Section 1
93
above, provided that you also meet all of these conditions:
94

  
95
    a) You must cause the modified files to carry prominent notices
96
    stating that you changed the files and the date of any change.
97

  
98
    b) You must cause any work that you distribute or publish, that in
99
    whole or in part contains or is derived from the Program or any
100
    part thereof, to be licensed as a whole at no charge to all third
101
    parties under the terms of this License.
102

  
103
    c) If the modified program normally reads commands interactively
104
    when run, you must cause it, when started running for such
105
    interactive use in the most ordinary way, to print or display an
106
    announcement including an appropriate copyright notice and a
107
    notice that there is no warranty (or else, saying that you provide
108
    a warranty) and that users may redistribute the program under
109
    these conditions, and telling the user how to view a copy of this
110
    License.  (Exception: if the Program itself is interactive but
111
    does not normally print such an announcement, your work based on
112
    the Program is not required to print an announcement.)
113

  
114
These requirements apply to the modified work as a whole.  If
115
identifiable sections of that work are not derived from the Program,
116
and can be reasonably considered independent and separate works in
117
themselves, then this License, and its terms, do not apply to those
118
sections when you distribute them as separate works.  But when you
119
distribute the same sections as part of a whole which is a work based
120
on the Program, the distribution of the whole must be on the terms of
121
this License, whose permissions for other licensees extend to the
122
entire whole, and thus to each and every part regardless of who wrote it.
123

  
124
Thus, it is not the intent of this section to claim rights or contest
125
your rights to work written entirely by you; rather, the intent is to
126
exercise the right to control the distribution of derivative or
127
collective works based on the Program.
128

  
129
In addition, mere aggregation of another work not based on the Program
130
with the Program (or with a work based on the Program) on a volume of
131
a storage or distribution medium does not bring the other work under
132
the scope of this License.
133

  
134
  3. You may copy and distribute the Program (or a work based on it,
135
under Section 2) in object code or executable form under the terms of
136
Sections 1 and 2 above provided that you also do one of the following:
137

  
138
    a) Accompany it with the complete corresponding machine-readable
139
    source code, which must be distributed under the terms of Sections
140
    1 and 2 above on a medium customarily used for software interchange; or,
141

  
142
    b) Accompany it with a written offer, valid for at least three
143
    years, to give any third party, for a charge no more than your
144
    cost of physically performing source distribution, a complete
145
    machine-readable copy of the corresponding source code, to be
146
    distributed under the terms of Sections 1 and 2 above on a medium
147
    customarily used for software interchange; or,
148

  
149
    c) Accompany it with the information you received as to the offer
150
    to distribute corresponding source code.  (This alternative is
151
    allowed only for noncommercial distribution and only if you
152
    received the program in object code or executable form with such
153
    an offer, in accord with Subsection b above.)
154

  
155
The source code for a work means the preferred form of the work for
156
making modifications to it.  For an executable work, complete source
157
code means all the source code for all modules it contains, plus any
158
associated interface definition files, plus the scripts used to
159
control compilation and installation of the executable.  However, as a
160
special exception, the source code distributed need not include
161
anything that is normally distributed (in either source or binary
162
form) with the major components (compiler, kernel, and so on) of the
163
operating system on which the executable runs, unless that component
164
itself accompanies the executable.
165

  
166
If distribution of executable or object code is made by offering
167
access to copy from a designated place, then offering equivalent
168
access to copy the source code from the same place counts as
169
distribution of the source code, even though third parties are not
170
compelled to copy the source along with the object code.
171

  
172
  4. You may not copy, modify, sublicense, or distribute the Program
173
except as expressly provided under this License.  Any attempt
174
otherwise to copy, modify, sublicense or distribute the Program is
175
void, and will automatically terminate your rights under this License.
176
However, parties who have received copies, or rights, from you under
177
this License will not have their licenses terminated so long as such
178
parties remain in full compliance.
179

  
180
  5. You are not required to accept this License, since you have not
181
signed it.  However, nothing else grants you permission to modify or
182
distribute the Program or its derivative works.  These actions are
183
prohibited by law if you do not accept this License.  Therefore, by
184
modifying or distributing the Program (or any work based on the
185
Program), you indicate your acceptance of this License to do so, and
186
all its terms and conditions for copying, distributing or modifying
187
the Program or works based on it.
188

  
189
  6. Each time you redistribute the Program (or any work based on the
190
Program), the recipient automatically receives a license from the
191
original licensor to copy, distribute or modify the Program subject to
192
these terms and conditions.  You may not impose any further
193
restrictions on the recipients' exercise of the rights granted herein.
194
You are not responsible for enforcing compliance by third parties to
195
this License.
196

  
197
  7. If, as a consequence of a court judgment or allegation of patent
198
infringement or for any other reason (not limited to patent issues),
199
conditions are imposed on you (whether by court order, agreement or
200
otherwise) that contradict the conditions of this License, they do not
201
excuse you from the conditions of this License.  If you cannot
202
distribute so as to satisfy simultaneously your obligations under this
203
License and any other pertinent obligations, then as a consequence you
204
may not distribute the Program at all.  For example, if a patent
205
license would not permit royalty-free redistribution of the Program by
206
all those who receive copies directly or indirectly through you, then
207
the only way you could satisfy both it and this License would be to
208
refrain entirely from distribution of the Program.
209

  
210
If any portion of this section is held invalid or unenforceable under
211
any particular circumstance, the balance of the section is intended to
212
apply and the section as a whole is intended to apply in other
213
circumstances.
214

  
215
It is not the purpose of this section to induce you to infringe any
216
patents or other property right claims or to contest validity of any
217
such claims; this section has the sole purpose of protecting the
218
integrity of the free software distribution system, which is
219
implemented by public license practices.  Many people have made
220
generous contributions to the wide range of software distributed
221
through that system in reliance on consistent application of that
222
system; it is up to the author/donor to decide if he or she is willing
223
to distribute software through any other system and a licensee cannot
224
impose that choice.
225

  
226
This section is intended to make thoroughly clear what is believed to
227
be a consequence of the rest of this License.
228

  
229
  8. If the distribution and/or use of the Program is restricted in
230
certain countries either by patents or by copyrighted interfaces, the
231
original copyright holder who places the Program under this License
232
may add an explicit geographical distribution limitation excluding
233
those countries, so that distribution is permitted only in or among
234
countries not thus excluded.  In such case, this License incorporates
235
the limitation as if written in the body of this License.
236

  
237
  9. The Free Software Foundation may publish revised and/or new versions
238
of the General Public License from time to time.  Such new versions will
239
be similar in spirit to the present version, but may differ in detail to
240
address new problems or concerns.
241

  
242
Each version is given a distinguishing version number.  If the Program
243
specifies a version number of this License which applies to it and "any
244
later version", you have the option of following the terms and conditions
245
either of that version or of any later version published by the Free
246
Software Foundation.  If the Program does not specify a version number of
247
this License, you may choose any version ever published by the Free Software
248
Foundation.
249

  
250
  10. If you wish to incorporate parts of the Program into other free
251
programs whose distribution conditions are different, write to the author
252
to ask for permission.  For software which is copyrighted by the Free
253
Software Foundation, write to the Free Software Foundation; we sometimes
254
make exceptions for this.  Our decision will be guided by the two goals
255
of preserving the free status of all derivatives of our free software and
256
of promoting the sharing and reuse of software generally.
257

  
258
			    NO WARRANTY
259

  
260
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
262
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
266
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
267
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
REPAIR OR CORRECTION.
269

  
270
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
POSSIBILITY OF SUCH DAMAGES.
279

  
280
		     END OF TERMS AND CONDITIONS
281

  
282
	    How to Apply These Terms to Your New Programs
283

  
284
  If you develop a new program, and you want it to be of the greatest
285
possible use to the public, the best way to achieve this is to make it
286
free software which everyone can redistribute and change under these terms.
287

  
288
  To do so, attach the following notices to the program.  It is safest
289
to attach them to the start of each source file to most effectively
290
convey the exclusion of warranty; and each file should have at least
291
the "copyright" line and a pointer to where the full notice is found.
292

  
293
    <one line to give the program's name and a brief idea of what it does.>
294
    Copyright (C) <year>  <name of author>
295

  
296
    This program is free software; you can redistribute it and/or modify
297
    it under the terms of the GNU General Public License as published by
298
    the Free Software Foundation; either version 2 of the License, or
299
    (at your option) any later version.
300

  
301
    This program is distributed in the hope that it will be useful,
302
    but WITHOUT ANY WARRANTY; without even the implied warranty of
303
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
304
    GNU General Public License for more details.
305

  
306
    You should have received a copy of the GNU General Public License along
307
    with this program; if not, write to the Free Software Foundation, Inc.,
308
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309

  
310
Also add information on how to contact you by electronic and paper mail.
311

  
312
If the program is interactive, make it output a short notice like this
313
when it starts in an interactive mode:
314

  
315
    Gnomovision version 69, Copyright (C) year name of author
316
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317
    This is free software, and you are welcome to redistribute it
318
    under certain conditions; type `show c' for details.
319

  
320
The hypothetical commands `show w' and `show c' should show the appropriate
321
parts of the General Public License.  Of course, the commands you use may
322
be called something other than `show w' and `show c'; they could even be
323
mouse-clicks or menu items--whatever suits your program.
324

  
325
You should also get your employer (if you work as a programmer) or your
326
school, if any, to sign a "copyright disclaimer" for the program, if
327
necessary.  Here is a sample; alter the names:
328

  
329
  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330
  `Gnomovision' (which makes passes at compilers) written by James Hacker.
331

  
332
  <signature of Ty Coon>, 1 April 1989
333
  Ty Coon, President of Vice
334

  
335
This General Public License does not permit incorporating your program into
336
proprietary programs.  If your program is a subroutine library, you may
337
consider it more useful to permit linking proprietary applications with the
338
library.  If this is what you want to do, use the GNU Lesser General
339
Public License instead of this License.
MANIFEST.in
1
include Makefile
2
include setup.py
3
include larpectl
4
include apache2-vhost-larpe
5
include apache2.conf
6
include larpe-reload-apache2-script
7
include larpe-reload-apache2.c
8
include README COPYING MANIFEST.in MANIFEST NEWS AUTHORS
9
recursive-include larpe *.py *.ptl
10
recursive-include data *
11
recursive-include root *
12
recursive-include po *.po *.pot Makefile
13
recursive-include doc *.rst Makefile *.png *.sty custom.tex *.py *.sh *.css
Makefile
1
prefix = /usr
2
config_prefix = /var
3
config_dir = $(config_prefix)/lib/larpe
4

  
5
INSTALL = /usr/bin/install -c
6
PYTHON = /usr/bin/python
7

  
8
LARPE_VERSION = 0.2.9
9

  
10
larpe-reload-apache2: larpe-reload-apache2.c
11

  
12
install: larpe-reload-apache2
13
	rm -rf build
14
	$(MAKE) -C po install
15
	$(PYTHON) setup.py install --root "$(DESTDIR)/" --prefix "$(prefix)" --no-compile
16
	$(INSTALL) -d $(DESTDIR)$(prefix)/sbin/
17
	$(INSTALL) larpectl $(DESTDIR)$(prefix)/sbin/
18
	$(INSTALL) -m 4550 larpe-reload-apache2 $(DESTDIR)$(prefix)/sbin/
19
	$(INSTALL) -d $(DESTDIR)/etc/larpe
20
	$(INSTALL) -m 644 conf/apache2-vhost-larpe-common $(DESTDIR)/etc/larpe
21
	$(INSTALL) -d $(DESTDIR)$(config_dir)
22
	$(INSTALL) -d $(DESTDIR)$(config_dir)/vhosts.d
23
	$(INSTALL) -d $(DESTDIR)$(config_dir)/vhost-locations.d
24
	$(INSTALL) -d $(DESTDIR)$(config_dir)/vhosts.d.disabled
25
	$(INSTALL) -d $(DESTDIR)$(config_dir)/vhost-locations.d.disabled
26

  
27
uninstall:
28
	$(MAKE) -C po uninstall
29
	-rm -f $(DESTDIR)$(prefix)/sbin/larpe-reload-apache2
30
	-rm -f $(DESTDIR)$(prefix)/sbin/larpe-reload-apache2-script
31
	-rm -f $(DESTDIR)$(prefix)/sbin/larpectl
32
	-rm -rf $(DESTDIR)$(prefix)/share/larpe/
33
	@echo
34
	@echo "Depending on your Python version, you will have to remove manually the files in /usr/lib/python(version)/site-packages/larpe/"
35

  
36
clean:
37
	$(MAKE) -C po clean
38
	$(MAKE) -C doc clean
39
	-$(PYTHON) setup.py clean
40
	-rm -f larpe-reload-apache2
41

  
42
dist: clean
43
	tar czf dist/larpe-$(LARPE_VERSION).tar.gz -C .. --transform s/trunk/larpe-$(LARPE_VERSION)/ --exclude-from=exclude_from_dist trunk
44

  
45
.PHONY: clean dist
NEWS
1
NEWS
2
====
3

  
4
Version 1.0
5
-----------
6

  
7
- Adds Liberty Alliance to some sites without modying sites code
8
- SAML 2.0 and ID-FF 1.2 support (authentication and logout)
9
- Form prefilling with ID-WSF 2.0
10
- Configuration assistant (wizard-like) for configuring new sites
11
- Fully tested for several sites with very different behaviours
12
- Plugin system to handle specific behaviour of some sites
13
- Automatic Apache 2 configuration
14
- Automatic creation of Apache python filters to transform authentication boxes on the sites
15
- Support for proxies
16
- Logging and debug options
17
- Packages for Debian and Fedora (and children of both) distributions
README
1
Larpe - Liberty Alliance Reverse Proxy
2
======================================
3

  
4
Description
5
-----------
6

  
7
Larpe is a Liberty Alliance Reverse Proxy.  It allows any service provider (that
8
is a website) to use Liberty Alliance features (Identity federation, Single
9
Sign On and Single Sign Logout) without changing the code of the service
10
provider itself.  It uses the Lasso library which is certified by the Liberty
11
Alliance consortium.
12

  
13

  
14
Documentation
15
-------------
16

  
17
* README, as you are doing;
18

  
19
* doc/en/ for English documentation, published as HTML on 
20
  http://larpe.labs.libre-entreprise.org/doc/en/larpe-admin.html
21

  
22

  
23
Copyright
24
---------
25

  
26
Larpe is copyrighted by Entr'ouvert and is licensed under the GNU General
27
Public Licence.  Artwork and administrative design are from DotClear and
28
released under the GNU General Public License by Olivier Meunier and others.
29
Some artwork comes from GTK+ (LGPL).
30

  
31
Read the COPYING file for the complete license text.  Read the AUTHORS file for
32
additional credits.
TODO
1
- Tests
2
  - egroupware
3
  - http://labs.libre-entreprise.org/
4
  - logs.entrouvert.org
5

  
6
====== Roadmap de Larpe ======
7

  
8
===== 0.2 =====
9

  
10
  * Vérifier la compatibilité avec egroupware
11
  * 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
12
  * Supprimer debconf
13
  * Vérification des formulaires de configuration d'hôtes
14
    * Tests de valeurs erronés diverses
15
    * Erreur si on donne un label qui existe deja
16
  * Ne plus inclure le binaire larpe-reload-apache2 dans les sources
17
  * Corriger les avertissements debian
18
  * Ne pas demander la clé publique de l'idp
19
  * Compléter les traductions
20
  * Ajouter la possibilité de changer la langue dans l'interface d'administration
21
  * Mettre à jour la documentation
22
    * Ajouter un chapitre sur les sites testés et leurs options de configuration particulières
23
  * Traduire la documentation en français
24

  
25
===== 0.3 =====
26

  
27
  * Implémenter le SLO en SOAP
28
  * Supprimer un /liberty/ des urls
29
  * Voir comment activer le SSLProxyEngine quand on utilise un sous répertoire
30
  * Faire un site web pour présenter Larpe
31
  * Ajouter la possibilité d'envoyer les exceptions par courriel à l'administrateur
32
  * Améliorer la journalisation des accès et des erreurs
33

  
34
===== 1.0 =====
35

  
36
  * Support de SAML 2.0
37
  * Implémenter l'accès à un site nécessitant une authentification préalable avant tout accès
38
    * Choix de cette fonctionnalité par une option de configuration par site
39
  * 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
40
  * Documentation technique pour les développeurs ?
41

  
42
===== Non classés =====
43

  
44
  * Support des sites qui ont une authentification HTTP (à priori, nécessite de charger toute la configuration de larpe dans le filtre python d'apache)
45
  * Création de nouveaux comptes pour les sites, avec des jetons (déjà implémenté en partie ; est-ce utile ?)
46

  
47
Fait
48
====
49

  
50
- Serveur python principal
51
  - Fonctionnalités liberty
52
    - SSO (depuis le sp et depuis l'idp)
53
    - Fédération
54
    - SLO (depuis le sp et depuis l'idp)
55
    - Défédération (depuis l'idp) en SOAP et redirect
56
  - Support https
57
  - Possibilité d'utiliser toutes les combinaisons de sous domaines et de sous répertoires
58
    - RP par vhost (appli1.example.com, rp de appli1.interne)
59
    - RP par repertoire (www.example.com/appli1, rp de appl1.interne)
60
  - Récupère la configuration de l'IP des vhosts
61

  
62
- Administration
63
  - Authentification liberty sur l'admin
64
  - Créer de nouveaux sites (+ modifier, supprimer)
65
  - Écrire les vhosts correspondants
66
  - Rechargement de la configuration d'apache
67
    - Script + wrapper en C suid root
68
  - Gestion d'utilisateurs pour administrer le RP (Authentification http)
69
  - Gestion des traductions
70

  
71
- Filtre Python branché en sortie sur Apache à la suite du filtre de réécriture html (proxy_html)
72
  - Générique
73
  - Personalisable par site pour une meilleure intégration dans les pages
74

  
75
- Sites testés
76
  - Dotclear
77
  - Dacode
78
    - linuxfr.org
79
  - Sympa
80
    - listes.entrouvert.com
81
    - listes.libre-entreprise.org  
82
  - Mediawiki
83
    - all4dev.libre-entreprise.org
84
    - www.libre-entreprise.org
85
  - www.besancon.com
86
  - Egroupware
87
    - quintine.entrouvert.org/egroupware/
88
  - squirrelmail
89
  - Concerto Espace-famille
90
  - Ciril Net RH
91
  - Agirhe
92

  
93
- Documentation
94

  
95
- Paquets Debian
96
  - Debconf pour demander le nom de domaine et le courriel de l'admin, ainsi que le compte administrateur
97

  
98
- Installation sur lupin
99

  
100
- Batterie de tests de non-regression
101

  
conf/apache2-vhost-larpe
1
<VirtualHost *>
2
    ServerName localhost
3
    ServerAdmin root@localhost
4

  
5
    include /etc/larpe/apache2-vhost-larpe-common
6
    include /var/lib/larpe/vhost-locations.d
7

  
8
    CustomLog /var/log/apache2/larpe-access.log combined
9
    ErrorLog /var/log/apache2/larpe-error.log
10
</VirtualHost>
11

  
12
include /var/lib/larpe/vhosts.d
conf/apache2-vhost-larpe-common
1
# Static files
2
DocumentRoot /usr/share/larpe/web/
3

  
4
# Python application
5
SCGIMount / 127.0.0.1:3007
6

  
7
# Static files for larpe
8
<Location /larpe/>
9
    ProxyPass   !
10
    SCGIHandler off
11
</Location>
12

  
13
# Larpe python application
14
<Location /liberty/>
15
    ProxyPass   !
16
</Location>
17

  
18
# No gzip compression
19
RequestHeader unset Accept-Encoding
conf/filters/output_ciril_net_rh.py
1
import re
2
import os
3
import pickle
4

  
5
from larpe import sessions
6
from mod_python import Cookie
7

  
8
def is_auth_ok(req):
9
    """ Test if you are authenticate on the Larpe server """
10
    cookies = Cookie.get_cookies(req, Cookie.MarshalCookie, secret='secret007')
11
    sessions_dir = os.path.join("%(larpe_dir)s", "sessions")
12
    for name, cookie in cookies.iteritems():
13
        value = cookie.value.replace('"', '')
14
        if "larpe-" in name and value in os.listdir(sessions_dir):
15
            try:
16
                file = open(os.path.join(sessions_dir, value), "rb")
17
                session = pickle.load(file)
18
                if not session.users or not session.id:
19
                    return False
20
                return True
21
            except Exception, err:
22
                return False
23
    return False
24

  
25

  
26
def filter_page(filter, page):
27
    r = re.compile(r"""(<a.*?href=["']).*?(["'].*?>D.*?connexion</a>)""")
28
    page = r.sub(r"""\1%(logout_url)s\2""", page)
29
    return page
30

  
31
def outputfilter(filter):
32
    """ Apache called this function by default """
33
    if not re.search("^/liberty/.*", filter.req.uri) and not is_auth_ok(filter.req):
34
        filter.write('<meta http-equiv="refresh" content="0; url=%(login_url)s" />')
35
        filter.close()
36
        return
37

  
38
    if filter.req.content_type is not None:
39
        is_html = re.search('text/html', filter.req.content_type)
40
    if filter.req.content_type is None or not is_html:
41
        filter.pass_on()
42
    else:
43
        if not hasattr(filter.req, 'temp_doc'):
44
            # Create a new attribute to hold the document
45
            filter.req.temp_doc = []
46
            # If content-length ended up wrong, Gecko browsers truncated data
47
            if 'Content-Length' in filter.req.headers_out:
48
                del filter.req.headers_out['Content-Length']
49

  
50
        temp_doc = filter.req.temp_doc
51
        s = filter.read()
52
        # Could get '' at any point, but only get None at end
53
        while s:
54
            temp_doc.append(s)
55
            s = filter.read()
56

  
57
        # The end
58
        if s is None:
59
            page = ''.join(temp_doc)
60
            page = filter_page(filter, page)
61
            filter.write(page)
62
            filter.close()
63

  
conf/filters/output_replace_form.py
1
import re
2

  
3
def filter_page(filter, page):
4
    current_form = re.compile('<form [^>]*?action="%(auth_form_action)s".*?>.*?</form>', re.DOTALL)
5
    page = current_form.sub('<form method="post" action="/liberty/%(name)s/login"><input type="submit" value="Connexion" /></form>', page)
6
    return page
7

  
8
def outputfilter(filter):
9
    # Only filter html code
10
    if filter.req.content_type is not None:
11
        is_html = re.search('text/html', filter.req.content_type)
12
    if filter.req.content_type is None or not is_html:
13
        filter.pass_on()
14
    else:
15
        if not hasattr(filter.req, 'temp_doc'):
16
            # Create a new attribute to hold the document
17
            filter.req.temp_doc = []
18
            # If content-length ended up wrong, Gecko browsers truncated data
19
            if 'Content-Length' in filter.req.headers_out:
20
                del filter.req.headers_out['Content-Length']
21

  
22
        temp_doc = filter.req.temp_doc
23
        s = filter.read()
24
        # Could get '' at any point, but only get None at end
25
        while s:
26
            temp_doc.append(s)
27
            s = filter.read()
28

  
29
        # The end
30
        if s is None:
31
            page = ''.join(temp_doc)
32
            page = filter_page(filter, page)
33
            filter.write(page)
34
            filter.close()
35

  
debian/changelog
1
larpe (1.1.1-1) unstable; urgency=low
2

  
3
  * Removing a useless import
4
  * Change Debian maintainer
5
  * Change architecture from any to all
6
  * Using pysupport instead of pycentral
7
  * Update Debian package dependencies
8

  
9
 -- Jerome Schneider <jschneider@entrouvert.com>  Mon, 19 Jul 2010 16:18:28 +0200
10

  
11
larpe (1.1-1) unstable; urgency=low
12

  
13
  * New release
14
    - Rewrite filter management
15
    - Filters are now customisable
16
    - Improve Ciril module
17
    - Improve plugins management
18
    - Support multi filters
19
    - Fix SAML 2 logout
20
    - Fix site authentification plugins management
21
    - Fix sessions management
22
    - Code cleaning
23

  
24
 -- Jerome Schneider <jschneider@entrouvert.com>  Mon, 19 Jul 2010 11:52:47 +0200
25

  
26
larpe (1.0-1) unstable; urgency=low
27

  
28
  * New release
29
    - SAML 2.0 and ID-FF 1.2 support (authentication and logout)
30
    - Form prefilling with ID-WSF 2.0
31
    - Configuration assistant (wizard-like) for configuring new sites
32
    - Fully tested for several sites with very different behaviours
33
    - Plugin system to handle specific behaviour of some sites
34
    - Automatic Apache 2 configuration
35
    - Automatic creation of Apache python filters to transform authentication
36
      boxes on the sites
37
    - Support for proxies
38
    - Logging and debug options
39

  
40
 -- Damien Laniel <dlaniel@entrouvert.com>  Mon, 09 Mar 2009 11:19:49 +0100
41

  
42
larpe (0.2.1-1) unstable; urgency=low
43

  
44
  * New release
45

  
46
 -- Damien Laniel <dlaniel@entrouvert.com>  Wed, 20 Jun 2007 15:43:16 +0200
47

  
48
larpe (0.2.0-1) unstable; urgency=low
49

  
50
  * New release
51

  
52
 -- Damien Laniel <dlaniel@entrouvert.com>  Tue, 30 Jan 2007 18:07:04 +0100
53

  
54
larpe (0.1.1-2) unstable; urgency=low
55

  
56
  * Use python2.4
57

  
58
 -- Damien Laniel <dlaniel@entrouvert.com>  Tue, 19 Dec 2006 17:21:05 +0100
59

  
60
larpe (0.1.1-1) unstable; urgency=low
61

  
62
  * New release
63

  
64
 -- Damien Laniel <dlaniel@entrouvert.com>  Thu,  5 Oct 2006 11:47:53 +0200
65

  
66
larpe (0.1.0-1) unstable; urgency=low
67

  
68
  * New release
69

  
70
 -- Damien Laniel <dlaniel@entrouvert.com>  Wed,  4 Oct 2006 10:19:26 +0200
71

  
72
larpe (0.0.4-1) unstable; urgency=low
73

  
74
  * New version, many improvements, more compatible sites, some bug fixes
75

  
76
 -- Damien Laniel <dlaniel@entrouvert.com>  Tue,  3 Oct 2006 20:44:06 +0200
77

  
78
larpe (0.0.3-1) unstable; urgency=low
79

  
80
  * New version, many improvements, more compatible sites, some bug fixes
81

  
82
 -- Damien Laniel <dlaniel@entrouvert.com>  Mon, 25 Sep 2006 11:11:36 +0200
83

  
84
larpe (0.0.2-1) unstable; urgency=low
85

  
86
  * Initial package.
87

  
88
 -- Damien Laniel <dlaniel@entrouvert.com>  Fri, 08 Sep 2006 16:00:00 +0200
89

  
debian/compat
1
5
debian/config
1
#!/bin/sh -e
2

  
3
# Source debconf library.
4
. /usr/share/debconf/confmodule
5

  
6
# Hostname
7
#db_input high larpe/hostname || true
8
#db_go
9

  
10
# Administrator email address
11
#db_input medium larpe/admin_email || true
12
#db_go
13

  
14
# Enable this vhost
15
#db_input high larpe/enable_vhost || true
16
#db_go
17

  
18
# Administrator login
19
#db_input high larpe/admin_username || true
20
#db_go
21

  
22
# Administrator password
23
#db_input high larpe/admin_password || true
24
#db_go
debian/control
1
Source: larpe
2
Section: web
3
Priority: optional
4
Maintainer: Jérôme Schneider <jschneider@entrouvert.com>
5
Build-Depends: debhelper (>= 5.0.37.2), python, python-support (>= 0.4), gettext, python-quixote (>= 2.5)
6
Standards-Version: 3.8.0
7

  
8
Package: larpe
9
Architecture: all
10
Depends: ${shlibs:Depends}, ${python:Depends}, python-quixote (>= 2.5), python-lasso (>= 2.2.1), python-scgi, python-libxml2, apache2, libapache2-mod-scgi, libapache2-mod-python, libapache2-mod-proxy-html
11
XB-Python-Version: ${python:Versions}
12
Description: Liberty Alliance Reverse Proxy
13
 Larpe allows any service provider (that is a website) to use Liberty Alliance
14
 identity management and Single Sign On features without changing the code of
15
 the service provider itself.
16
 .
debian/copyright
1
This package was debianized by Damien Laniel <dlaniel@entrouvert.com> on
2
Fri, 08 Sep 2006 16:00:00 +0200.
3

  
4
Upstream Author: Damien Laniel <dlaniel@entrouvert.com>
5

  
6
Copyright (c) 2005 Entr'ouvert;
7
copyright (c) 2003-2005 dotclear for some graphics.
8

  
9
License is GNU GPL v2 or later plus OpenSSL exception clause.
10

  
11
This program is free software; you can redistribute it and/or
12
modify it under the terms of the GNU General Public License
13
as published by the Free Software Foundation; either version 2
14
of the License, or (at your option) any later version.
15

  
16
This program is distributed in the hope that it will be useful,
17
but WITHOUT ANY WARRANTY; without even the implied warranty of
18
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
GNU General Public License for more details.
20

  
21
You should have received a copy of the GNU General Public License
22
along with this program; if not, write to the Free Software
23
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
24

  
25
On Debian GNU/Linux systems, the complete text of the GNU General Public
26
License can be found in `/usr/share/common-licenses/GPL'.
27

  
debian/dirs
1
etc/apache2/sites-available
2
etc/larpe
3
usr/sbin
4
var/lib/larpe
debian/docs
1
README
2
AUTHORS
debian/init
1
#! /bin/sh
2
### BEGIN INIT INFO
3
# Provides:          larpe
4
# Required-Start:    $local_fs $network
5
# Required-Stop:     $local_fs $network
6
# Default-Start:     2 3 4 5
7
# Default-Stop:      0 1 6
8
# Short-Description: Start Larpe Liberty Alliance reverse proxy
9
# Description:       Start Larpe Liberty Alliance reverse proxy
10
### END INIT INFO
11

  
12
set -e
13

  
14
# Gracefully exit if the package has been removed.
15
test -x $DAEMON || exit 0
16

  
17
# Source function library
18
. /lib/lsb/init-functions
19

  
20
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
21
DESC="larpe"
22
NAME=larpe
23
DAEMON=/usr/sbin/larpectl
24
PIDFILE=/var/run/$NAME.pid
25
SCRIPTNAME=/etc/init.d/$NAME
26

  
27

  
28
# Read config file if it is present.
29
if [ -r /etc/default/$NAME ]
30
then
31
    . /etc/default/$NAME
32
fi
33

  
34
#
35
# Function that starts the daemon/service.
36
#
37
d_start() {
38
    start-stop-daemon --start --quiet --pidfile $PIDFILE --oknodo \
39
		--chuid www-data:www-data --make-pidfile --background --exec $DAEMON -- start $OPTIONS
40
}
41

  
42
#
43
# Function that stops the daemon/service.
44
#
45
d_stop() {
46
    start-stop-daemon --stop --quiet --pidfile $PIDFILE --oknodo
47
    rm -f $PIDFILE
48
}
49

  
50
case "$1" in
51
    start)
52
        log_begin_msg "Starting $DESC: $NAME"
53
        d_start
54
        log_end_msg $?
55
        ;;
56

  
57
    stop)
58
        log_begin_msg "Stopping $DESC: $NAME"
59
        d_stop
60
        log_end_msg $?
61
        ;;
62

  
63
    restart|force-reload)
64
    #
65
    #	If the "reload" option is implemented, move the "force-reload"
66
    #	option to the "reload" entry above. If not, "force-reload" is
67
    #	just the same as "restart".
68
    #
69
        log_begin_msg "Restarting $DESC: $NAME"
70
        d_stop
71
        sleep 1
72
        d_start
73
        log_end_msg $?
74
        ;;
75
    
76
    *)
77
        echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
78
        exit 1
79
        ;;
80
esac
81

  
82
exit 0
debian/larpe-reload-apache2-script
1
#!/bin/sh
2

  
3
/etc/init.d/apache2 reload
debian/postinst
1
#! /bin/sh
2
# postinst script for larpe
3
#
4
# see: dh_installdeb(1)
5

  
6
set -e
7

  
8
# summary of how this script can be called:
9
#        * <postinst> `configure' <most-recently-configured-version>
10
#        * <old-postinst> `abort-upgrade' <new version>
11
#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
12
#          <new-version>
13
#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
14
#          <failed-install-package> <version> `removing'
15
#          <conflicting-package> <version>
16
# for details, see http://www.debian.org/doc/debian-policy/ or
17
# the debian-policy package
18
#
19
# quoting from the policy:
20
#     Any necessary prompting should almost always be confined to the
21
#     post-installation script, and should be protected with a conditional
22
#     so that unnecessary prompting doesn't happen if a package's
23
#     installation fails and the `postinst' is called with `abort-upgrade',
24
#     `abort-remove' or `abort-deconfigure'.
25

  
26
PACKAGE=larpe
27
VERSION=2.4
28
LIB="/usr/lib/python$VERSION"
29
DIRLIST="/usr/share/pycentral/larpe/site-packages/larpe/"
30

  
31
case "$1" in
32
    configure|abort-upgrade|abort-remove|abort-deconfigure)
33
        for i in $DIRLIST ; do
34
            /usr/bin/python$VERSION -O $LIB/compileall.py -q $i
35
            /usr/bin/python$VERSION $LIB/compileall.py -q $i
36
        done
37

  
38
        # Load Apache 2 modules
39
        for module in "proxy" "rewrite" "headers" "proxy_http"; do
40
            a2enmod ${module} > /dev/null || true
41
        done
42

  
43
        # Restart Apache 2
44
        set +e
45
        if [ -x /usr/sbin/invoke-rc.d ]; then
46
            invoke-rc.d apache2 restart || true
47
        else
48
            /etc/init.d/apache2 restart || true
49
        fi
50
        set -e
51
    ;;
52

  
53
    *)
54
        echo "postinst called with unknown argument \`$1'" >&2
55
        exit 1
56
    ;;
57
esac
58

  
59

  
60

  
61
# dh_installdeb will replace this with shell code automatically
62
# generated by other debhelper scripts.
63

  
64
#DEBHELPER#
65

  
66
exit 0
debian/prerm
1
#! /bin/sh
2
# prerm script for larpe
3
#
4
# see: dh_installdeb(1)
5

  
6
set -e
7

  
8
# summary of how this script can be called:
9
#        * <prerm> `remove'
10
#        * <old-prerm> `upgrade' <new-version>
11
#        * <new-prerm> `failed-upgrade' <old-version>
12
#        * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
13
#        * <deconfigured's-prerm> `deconfigure' `in-favour'
14
#          <package-being-installed> <version> `removing'
15
#          <conflicting-package> <version>
16
# for details, see http://www.debian.org/doc/debian-policy/ or
17
# the debian-policy package
18

  
19

  
20
PACKAGE=larpe
21

  
22
case "$1" in
23
    remove|upgrade|deconfigure)
24
    	dpkg --listfiles $PACKAGE |
25
                  awk '$0~/\.py$/ {print $0"c\n" $0"o"}' |
26
                  xargs rm -f >&2
27
        ;;
28
    failed-upgrade)
29
        ;;
30
    *)
31
        echo "prerm called with unknown argument \`$1'" >&2
32
        exit 1
33
    ;;
34
esac
35

  
36
# dh_installdeb will replace this with shell code automatically
37
# generated by other debhelper scripts.
38

  
39
#DEBHELPER#
40

  
41
exit 0
debian/pycompat
1
2
debian/rules
1
#!/usr/bin/make -f
2
# GNU copyright 1997 to 1999 by Joey Hess.
3

  
4
# Uncomment this to turn on verbose mode.
5
#export DH_VERBOSE=1
6

  
7
PYTHON=/usr/bin/python
8

  
9
LARPE_USER = www-data
10
LARPE_GROUP = www-data
11

  
12
ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS)))
13
	CFLAGS += -g
14
endif
15
ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
16
	INSTALL_PROGRAM += -s
17
endif
18

  
19
build: build-stamp
20

  
21
build-stamp:
22
	dh_testdir
23
	touch build-stamp
24

  
25
clean:
26
	dh_testdir
27
	dh_testroot
28
	rm -f build-stamp
29

  
30
	make clean
31

  
32
	dh_clean
33

  
34
install: build
35
	dh_testdir
36
	dh_testroot
37
	dh_clean -k
38
	dh_installdirs
39

  
40
	make install prefix=/usr DESTDIR=$(CURDIR)/debian/larpe
41
	# Apache Vhost
42
	dh_install conf/apache2-vhost-larpe etc/apache2/sites-available
43
	# Apache reload script
44
	dh_install debian/larpe-reload-apache2-script usr/sbin
45

  
46
	# Give files ownership to Larpe user and group
47
	chown -R $(LARPE_USER):$(LARPE_GROUP) $(CURDIR)/debian/larpe/usr/share/larpe/
48
	chown -R $(LARPE_USER):$(LARPE_GROUP) $(CURDIR)/debian/larpe/var/lib/larpe/
49
	chgrp $(LARPE_GROUP) $(CURDIR)/debian/larpe/usr/sbin/larpe-reload-apache2
50

  
51
# Build architecture-independent files here.
52
binary-indep: build install
53
# We have nothing to do by default.
54

  
55
# Build architecture-dependent files here.
56
binary-arch: build install
57
	dh_testdir
58
	dh_testroot
59
	dh_installdocs
60
	dh_installinit
61
	dh_installchangelogs
62
	dh_link
63
	dh_strip
64
	dh_compress
65
	dh_fixperms -X /var/lib/larpe -X /usr/sbin/larpe-reload-apache2
66
	dh_pysupport
67
	dh_installdeb
68
	dh_shlibdeps
69
	dh_gencontrol
70
	dh_md5sums
71
	dh_builddeb
72

  
73
binary: binary-indep binary-arch
74
.PHONY: build clean binary-indep binary-arch binary install
doc/Makefile
1
all:
2
	$(MAKE) -C en
3

  
4
clean:
5
	$(MAKE) -C en clean
6

  
7
.PHONY: clean
8

  
doc/en/Makefile
1
RST2HTML = rst2html
2
RST2LATEX = ../scripts/rst2latex.py
3
PDFLATEX = pdflatex
4
RM = rm -f
5

  
6
all: larpe-admin.pdf larpe-admin.html
7

  
8
%.html: %.rst
9
	$(RST2HTML) --stylesheet=default.css --link-stylesheet --language=en $? > $@
10

  
11
figures-no-alpha-stamp:
12
	-$(RM) -r figures-no-alpha/
13
	mkdir figures-no-alpha/
14
	for F in figures/*.png; do \
15
		../scripts/removealpha.sh $$F figures-no-alpha/`basename $$F`; \
16
	done
17
	touch figures-no-alpha-stamp
18

  
19
%.tex: %.rst #figures-no-alpha-stamp
20
	cat $? | sed -e 's/figures\//figures-no-alpha\//' \
21
			-e 's/ ::$$/ : ::/g' \
22
			-e 's/.. section-numbering:://' | $(RST2LATEX) --language=en > $@
23

  
24
%.pdf: %.tex custom.tex
25
	$(PDFLATEX) $?
26
	logfile=`echo "$@" |sed -r "s/(.*)....$$/\\1/"`.log; while [ -f "$$logfile" -a -n "`grep "Rerun to get cross-references right" $$logfile`" ]; do $(PDFLATEX) $< ; done
27

  
28
clean:
29
	-$(RM) *.aux *.toc *.log *.out
30
	-$(RM) larpe-admin.pdf
31
	-$(RM) larpe-admin.tex
32
	-$(RM) larpe-admin.html
33
	-$(RM) -r figures-no-alpha figures-no-alpha-stamp
34

  
35
.PHONY: all clean
doc/en/custom.tex
1
\usepackage{float,fancyhdr,lscape,sectsty,colortbl,color,lastpage,setspace}
2
\usepackage[perpage,bottom]{footmisc}
3
\usepackage[hang]{caption2}
4
\usepackage{marvosym}
5

  
6
\usepackage{float,url,listings,tocbibind,fancyhdr,calc,placeins}
7

  
8
\usepackage{palatino}
9
\usepackage[Glenn]{fncychap}
10

  
11
\pagestyle{fancy}
12
\fancyhead{}
13
\fancyfoot{}
14
\fancyhead[L]{Authentic}
15
\fancyhead[R]{Administrator Guide}
16
\fancyfoot[C]{Page \thepage}
17
\addtolength{\headheight}{1.6pt}
18

  
19
\setlength\parindent{0pt}
20
\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
21
\setlength\abovecaptionskip{0.1ex}
22

  
23
\makeatletter
24
\renewcommand{\maketitle}{\begin{titlepage}%
25
    \let\footnotesize\small
26
    \let\footnoterule\relax
27
    \parindent \z@
28
    \reset@font
29
    \null\vfil
30
    \begin{flushleft}
31
      \huge \@title
32
    \end{flushleft}
33
    \par
34
    \hrule height 1pt
35
    \par
36
    \begin{flushright}
37
      \LARGE \@author \par
38
    \end{flushright}
39
    \vskip 60\p@
40
    \vfil\null
41
  \end{titlepage}%
42
  \setcounter{footnote}{0}%
43
}
44
\makeatother
45

  
doc/en/default.css
1
body {
2
	font-family: sans-serif;
3
}
4

  
5

  
6
h1 a, h2 a, h3 a, h4 a {
7
	text-decoration: inherit;
8
	color: inherit;
9
}
10

  
11
pre.literal-block {
12
	background: #eee;
13
	border: 1px inset black;
14
	padding: 2px;
15
	margin: auto 10px;
16
	overflow: auto;
17
}
18

  
19
h1.title {
20
	text-align: center;
21
	background: #eef;
22
	border: 1px solid #aaf;
23
	letter-spacing: 1px;
24
}
25

  
26
div.section {
27
	margin-bottom: 2em;
28
}
29

  
30
div.section h1 {
31
	padding: 0 15px;
32
	background: #eef;
33
	border: 1px solid #aaf;
34
}
35

  
36
div.section h2 {
37
	padding: 0 15px;
38
	background: #eef;
39
	border: 1px solid #aaf;
40
}
41

  
42
div.document {
43
	margin-top: 1em;
44
	border-top: 1px solid #aaf;
45
	border-bottom: 1px solid #aaf;
46
}
47

  
48
div.section p,
49
div.section ul {
50
	text-align: justify;
51
}
52

  
53
div.contents {
54
	float: right;
55
	border: 1px solid black;
56
	margin: 1em;
57
	background: #eef;
58
	max-width: 33%;
59
}
60

  
61
div#building-liberty-services-with-lasso div#table-of-contents {
62
	max-width: inherit;
63
	float: none;
64
	background: white url(lasso.png) bottom right no-repeat;
65
}
66

  
67
div.contents ul {
68
	padding-left: 1em;
69
	list-style: none;
70
}
71

  
72
div.contents li {
73
	padding-bottom: 2px;
74
}
75

  
76
div.contents p {
77
	background: #ddf;
78
	text-align: center;
79
	border-bottom: 1px solid black;
80
	margin: 0;
81
}
82

  
83
th.docinfo-name {
84
	text-align: right;
85
	padding-right: 0.5em;
86
}
87

  
88
dd {
89
	margin-bottom: 1ex;
90
}
91

  
92
table.table {
93
	margin: 1ex 0;
94
	border-spacing: 0px;
95
}
96

  
97

  
98
table.table th {
99
	padding: 0px 1ex;
100
	background: #eef;
101
	font-weight: normal;
102
}
103

  
104

  
105
table.table td {
106
	padding: 0 0.5ex;
107
}
108

  
109
div.note, div.warning {
110
	padding: 0.3ex;
111
	padding-left: 60px;
112
	min-height: 50px;
113
	margin: 1ex 1em;
114
}
115

  
116
div.note {
117
	background: #ffa url(note.png) top left no-repeat;
118
	border: 1px solid #fd8;
119
}
120

  
121
div.warning {
122
	background: #ffd url(warning.png) top left no-repeat;
123
}
124

  
125
p.admonition-title {
126
	font-weight: bold;
127
	display: inline;
128
	display: none;
129
	padding-right: 1em;
130
}
131

  
132
div.figure {
133
	margin: 0 auto;
134
	width: 70%;
135
	min-width: 800px;
136
	text-align: center;
137
}
138

  
139
div.figure p.caption {
140
	font-style: italic;
141
	margin: 1ex 0 2em 0;
142
	text-align: center;
143
}
doc/en/fncychap.sty
1
%%% Copyright   Ulf A. Lindgren
2
%%%
3
%%% Note        Premission is granted to modify this file under
4
%%%             the condition that it is saved using another
5
%%%             file and package name.
6
%%%
7
%%% Revision    1.1 (1997)
8
%%%
9
%%%             Jan. 8th Modified package name base date option
10
%%%             Jan. 22th Modified FmN and FmTi for error in book.cls
11
%%%                  \MakeUppercase{#}->{\MakeUppercase#}
12
%%%             Apr. 6th Modified Lenny option to prevent undesired 
13
%%%                  skip of line.
14
%%%             Nov. 8th Fixed \@chapapp for AMS
15
%%%
16
%%% Revision    1.2 (1998)
17
%%%
18
%%%             Feb. 11th Fixed appendix problem related to Bjarne
19
%%%             Aug. 11th Fixed problem related to 11pt and 12pt 
20
%%%                  suggested by Tomas Lundberg. THANKS!
21
%%%
22
%%% Revision    1.3 (2004)
23
%%%             Sep. 20th problem with frontmatter, mainmatter and
24
%%%                  backmatter, pointed out by Lapo Mori
25
%%%
26
%%% Revision    1.31 (2004)
27
%%%             Sep. 21th problem with the Rejne definition streched text
28
%%%                  caused ugly gaps in the vrule aligned with the title
29
%%%                  text. Kindly pointed out to me by Hendri Adriaens
30
%%%
31
%%% Revision    1.32 (2005)
32
%%%             Jun. 23th compatibility problem with the KOMA class 'scrbook.cls'
33
%%%                  a remedy is a redefinition of '\@schapter' in
34
%%%                  line with that used in KOMA. The problem was pointed
35
%%%                  out to me by Mikkel Holm Olsen
36
%%%
37
%%% Revision    1.33 (2005)
38
%%%             Aug. 9th misspelled ``TWELV'' corrected, the error was pointed
39
%%%                  out to me by George Pearson
40
%%%
41

  
42

  
43
%%% Last modified   Aug. 9th 2005
44

  
45
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
46
\ProvidesPackage{fncychap}
47
             [2004/09/21 v1.33
48
                 LaTeX package (Revised chapters)]
49

  
50
%%%% DEFINITION OF Chapapp variables
51
\newcommand{\CNV}{\huge\bfseries}
52
\newcommand{\ChNameVar}[1]{\renewcommand{\CNV}{#1}}
53

  
54

  
55
%%%% DEFINITION OF TheChapter variables
56
\newcommand{\CNoV}{\huge\bfseries}
57
\newcommand{\ChNumVar}[1]{\renewcommand{\CNoV}{#1}}
58

  
59
\newif\ifUCN
60
\UCNfalse
61
\newif\ifLCN
62
\LCNfalse
63
\def\ChNameLowerCase{\LCNtrue\UCNfalse}
64
\def\ChNameUpperCase{\UCNtrue\LCNfalse}
65
\def\ChNameAsIs{\UCNfalse\LCNfalse}
66

  
67
%%%%% Fix for AMSBook 971008
68

  
69
\@ifundefined{@chapapp}{\let\@chapapp\chaptername}{}
70

  
71

  
72
%%%%% Fix for Bjarne and appendix 980211
73

  
74
\newif\ifinapp
75
\inappfalse
76
\renewcommand\appendix{\par
77
  \setcounter{chapter}{0}%
78
  \setcounter{section}{0}%
79
  \inapptrue%
80
  \renewcommand\@chapapp{\appendixname}%
81
  \renewcommand\thechapter{\@Alph\c@chapter}}
82

  
83
%%%%% Fix for frontmatter, mainmatter, and backmatter 040920
84

  
85
\@ifundefined{@mainmatter}{\newif\if@mainmatter \@mainmattertrue}{}
86

  
87
%%%%%
88

  
89

  
90

  
91
\newcommand{\FmN}[1]{%
92
\ifUCN
93
   {\MakeUppercase#1}\LCNfalse
94
\else
95
   \ifLCN
96
      {\MakeLowercase#1}\UCNfalse
97
   \else #1
98
   \fi
99
\fi}
100

  
101

  
102
%%%% DEFINITION OF Title variables
103
\newcommand{\CTV}{\Huge\bfseries}
104
\newcommand{\ChTitleVar}[1]{\renewcommand{\CTV}{#1}}
105

  
106
%%%% DEFINITION OF the basic rule width
107
\newlength{\RW}
108
\setlength{\RW}{1pt}
109
\newcommand{\ChRuleWidth}[1]{\setlength{\RW}{#1}}
110

  
111
\newif\ifUCT
112
\UCTfalse
113
\newif\ifLCT
114
\LCTfalse
115
\def\ChTitleLowerCase{\LCTtrue\UCTfalse}
116
\def\ChTitleUpperCase{\UCTtrue\LCTfalse}
117
\def\ChTitleAsIs{\UCTfalse\LCTfalse}
118
\newcommand{\FmTi}[1]{%
119
\ifUCT
120
   {\MakeUppercase#1}\LCTfalse
121
\else
122
   \ifLCT
123
      {\MakeLowercase#1}\UCTfalse
124
   \else {#1}
125
   \fi
126
\fi}
127

  
128

  
129

  
130
\newlength{\mylen}
131
\newlength{\myhi}
132
\newlength{\px}
133
\newlength{\py}
134
\newlength{\pyy}
135
\newlength{\pxx}
136

  
137

  
138
\def\mghrulefill#1{\leavevmode\leaders\hrule\@height #1\hfill\kern\z@}
139

  
140
\newcommand{\DOCH}{%
141
  \CNV\FmN{\@chapapp}\space \CNoV\thechapter
142
  \par\nobreak
143
  \vskip 20\p@
144
  }
145
\newcommand{\DOTI}[1]{%
146
    \CTV\FmTi{#1}\par\nobreak
147
    \vskip 40\p@
148
    }
149
\newcommand{\DOTIS}[1]{%
150
    \CTV\FmTi{#1}\par\nobreak
151
    \vskip 40\p@
152
    }
153

  
154
%%%%%% SONNY DEF
155

  
156
\DeclareOption{Sonny}{%
157
  \ChNameVar{\Large\sf}
158
  \ChNumVar{\Huge}
159
  \ChTitleVar{\Large\sf}
160
  \ChRuleWidth{0.5pt}
161
  \ChNameUpperCase
162
  \renewcommand{\DOCH}{%
163
    \raggedleft
164
    \CNV\FmN{\@chapapp}\space \CNoV\thechapter
165
    \par\nobreak
166
    \vskip 40\p@}
167
  \renewcommand{\DOTI}[1]{%
168
    \CTV\raggedleft\mghrulefill{\RW}\par\nobreak
169
    \vskip 5\p@
170
    \CTV\FmTi{#1}\par\nobreak
171
    \mghrulefill{\RW}\par\nobreak
172
    \vskip 40\p@}
173
  \renewcommand{\DOTIS}[1]{%
174
    \CTV\raggedleft\mghrulefill{\RW}\par\nobreak
175
    \vskip 5\p@
176
    \CTV\FmTi{#1}\par\nobreak
177
    \mghrulefill{\RW}\par\nobreak
178
    \vskip 40\p@}
179
}
180

  
181
%%%%%% LENNY DEF
182

  
183
\DeclareOption{Lenny}{%
184

  
185
  \ChNameVar{\fontsize{14}{16}\usefont{OT1}{phv}{m}{n}\selectfont}
186
  \ChNumVar{\fontsize{60}{62}\usefont{OT1}{ptm}{m}{n}\selectfont}
187
  \ChTitleVar{\Huge\bfseries\rm}
188
  \ChRuleWidth{1pt}
189
  \renewcommand{\DOCH}{%
190
    \settowidth{\px}{\CNV\FmN{\@chapapp}}
191
    \addtolength{\px}{2pt}
192
    \settoheight{\py}{\CNV\FmN{\@chapapp}}
193
    \addtolength{\py}{1pt}
194

  
195
    \settowidth{\mylen}{\CNV\FmN{\@chapapp}\space\CNoV\thechapter}
196
    \addtolength{\mylen}{1pt}
197
    \settowidth{\pxx}{\CNoV\thechapter}
198
    \addtolength{\pxx}{-1pt}
199

  
200
    \settoheight{\pyy}{\CNoV\thechapter}
201
    \addtolength{\pyy}{-2pt}
202
    \setlength{\myhi}{\pyy}
203
    \addtolength{\myhi}{-1\py}
204
    \par
205
    \parbox[b]{\textwidth}{%
206
    \rule[\py]{\RW}{\myhi}%
207
    \hskip -\RW%
208
    \rule[\pyy]{\px}{\RW}%
209
    \hskip -\px%
210
    \raggedright%
211
    \CNV\FmN{\@chapapp}\space\CNoV\thechapter%
212
    \hskip1pt%
213
    \mghrulefill{\RW}%
214
    \rule{\RW}{\pyy}\par\nobreak%
215
    \vskip -\baselineskip%
216
    \vskip -\pyy%
217
    \hskip \mylen%
218
    \mghrulefill{\RW}\par\nobreak%
219
    \vskip \pyy}%
220
    \vskip 20\p@}
221
 
222

  
223
  \renewcommand{\DOTI}[1]{%
224
    \raggedright
225
    \CTV\FmTi{#1}\par\nobreak
226
    \vskip 40\p@}
227

  
228
  \renewcommand{\DOTIS}[1]{%
229
    \raggedright
230
    \CTV\FmTi{#1}\par\nobreak
231
    \vskip 40\p@}
232
 }
233

  
234

  
235
%%%%%%% GLENN DEF
236

  
237

  
238
\DeclareOption{Glenn}{%
239
  \ChNameVar{\bfseries\Large\sf}
240
  \ChNumVar{\Huge}
241
  \ChTitleVar{\bfseries\Large\rm}
242
  \ChRuleWidth{1pt}
243
  \ChNameUpperCase
244
  \ChTitleUpperCase
245
  \renewcommand{\DOCH}{%
246
    \settoheight{\myhi}{\CTV\FmTi{Test}}
247
    \setlength{\py}{\baselineskip}
248
    \addtolength{\py}{\RW}
249
    \addtolength{\py}{\myhi}
250
    \setlength{\pyy}{\py}
251
    \addtolength{\pyy}{-1\RW}
252
     
253
    \raggedright
254
    \CNV\FmN{\@chapapp}\space\CNoV\thechapter
255
    \hskip 3pt\mghrulefill{\RW}\rule[-1\pyy]{2\RW}{\py}\par\nobreak}
256

  
257
  \renewcommand{\DOTI}[1]{%
258
    \addtolength{\pyy}{-4pt}
259
    \settoheight{\myhi}{\CTV\FmTi{#1}}
260
    \addtolength{\myhi}{\py}
261
    \addtolength{\myhi}{-1\RW}
262
    \vskip -1\pyy
263
    \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 2pt
264
    \raggedleft\CTV\FmTi{#1}\par\nobreak
265
    \vskip 80\p@}
266

  
267
\newlength{\backskip}
268
  \renewcommand{\DOTIS}[1]{%
269
%    \setlength{\py}{10pt}
270
%    \setlength{\pyy}{\py}
271
%    \addtolength{\pyy}{\RW}
272
%    \setlength{\myhi}{\baselineskip}
273
%    \addtolength{\myhi}{\pyy}
274
%    \mghrulefill{\RW}\rule[-1\py]{2\RW}{\pyy}\par\nobreak
275
%    \addtolength{}{}
276
%\vskip -1\baselineskip
277
%    \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 2pt
278
%    \raggedleft\CTV\FmTi{#1}\par\nobreak
279
%    \vskip 60\p@}
280
%% Fix suggested by Tomas Lundberg
281
    \setlength{\py}{25pt}  % eller vad man vill
282
    \setlength{\pyy}{\py}
283
    \setlength{\backskip}{\py}
284
    \addtolength{\backskip}{2pt}
285
    \addtolength{\pyy}{\RW}
286
    \setlength{\myhi}{\baselineskip}
287
    \addtolength{\myhi}{\pyy}
288
    \mghrulefill{\RW}\rule[-1\py]{2\RW}{\pyy}\par\nobreak
289
    \vskip -1\backskip
290
    \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 3pt %
291
    \raggedleft\CTV\FmTi{#1}\par\nobreak
292
    \vskip 40\p@}
293
 }
294

  
295
%%%%%%% CONNY DEF
296

  
297
\DeclareOption{Conny}{%
298
  \ChNameUpperCase
299
  \ChTitleUpperCase  
300
  \ChNameVar{\centering\Huge\rm\bfseries}
301
  \ChNumVar{\Huge}
302
  \ChTitleVar{\centering\Huge\rm}
303
  \ChRuleWidth{2pt}
304

  
305
  \renewcommand{\DOCH}{%
306
    \mghrulefill{3\RW}\par\nobreak
307
    \vskip -0.5\baselineskip
308
    \mghrulefill{\RW}\par\nobreak
309
    \CNV\FmN{\@chapapp}\space \CNoV\thechapter
310
    \par\nobreak
311
    \vskip -0.5\baselineskip
312
   }
313
  \renewcommand{\DOTI}[1]{%
314
    \mghrulefill{\RW}\par\nobreak
315
    \CTV\FmTi{#1}\par\nobreak
316
    \vskip 60\p@
317
    }
318
  \renewcommand{\DOTIS}[1]{%
319
    \mghrulefill{\RW}\par\nobreak
320
    \CTV\FmTi{#1}\par\nobreak
321
    \vskip 60\p@
322
    }
323
  }
324

  
325
%%%%%%% REJNE DEF
326

  
327
\DeclareOption{Rejne}{%
328

  
329
  \ChNameUpperCase
330
  \ChTitleUpperCase  
331
  \ChNameVar{\centering\Large\rm}
332
  \ChNumVar{\Huge}
333
  \ChTitleVar{\centering\Huge\rm}
334
  \ChRuleWidth{1pt}
335
  \renewcommand{\DOCH}{%
336
    \settoheight{\py}{\CNoV\thechapter}
337
    \parskip=0pt plus 1pt % Set parskip to default, just in case v1.31
338
    \addtolength{\py}{-1pt}
339
    \CNV\FmN{\@chapapp}\par\nobreak
340
    \vskip 20\p@
341
    \setlength{\myhi}{2\baselineskip}
342
    \setlength{\px}{\myhi}
343
    \addtolength{\px}{-1\RW}
344
    \rule[-1\px]{\RW}{\myhi}\mghrulefill{\RW}\hskip
345
    10pt\raisebox{-0.5\py}{\CNoV\thechapter}\hskip 10pt\mghrulefill{\RW}\rule[-1\px]{\RW}{\myhi}\par\nobreak
346
     \vskip -3\p@% Added -2pt vskip to correct for streched text v1.31
347
    }
348
  \renewcommand{\DOTI}[1]{%
349
    \setlength{\mylen}{\textwidth}
350
    \parskip=0pt plus 1pt % Set parskip to default, just in case v1.31
351
    \addtolength{\mylen}{-2\RW}
352
    {\vrule width\RW}\parbox{\mylen}{\CTV\FmTi{#1}}{\vrule width\RW}\par\nobreak%
353
    \vskip -3pt\rule{\RW}{2\baselineskip}\mghrulefill{\RW}\rule{\RW}{2\baselineskip}%
354
    \vskip 60\p@% Added -2pt in vskip to correct for streched text v1.31
355
    }
356
  \renewcommand{\DOTIS}[1]{%
357
    \setlength{\py}{\fboxrule}
358
    \setlength{\fboxrule}{\RW}
359
    \setlength{\mylen}{\textwidth}
360
    \addtolength{\mylen}{-2\RW}
361
    \fbox{\parbox{\mylen}{\vskip 2\baselineskip\CTV\FmTi{#1}\par\nobreak\vskip \baselineskip}} 
362
    \setlength{\fboxrule}{\py}
363
    \vskip 60\p@
364
    }
365
  }
366

  
367

  
368
%%%%%%% BJARNE DEF
369

  
370
\DeclareOption{Bjarne}{%
371
  \ChNameUpperCase
372
  \ChTitleUpperCase  
373
  \ChNameVar{\raggedleft\normalsize\rm}
374
  \ChNumVar{\raggedleft \bfseries\Large}
375
  \ChTitleVar{\raggedleft \Large\rm}
376
  \ChRuleWidth{1pt}
377

  
378

  
379
%% Note thechapter -> c@chapter fix appendix bug
380
%% Fixed misspelled 12
381

  
382
  \newcounter{AlphaCnt}
383
  \newcounter{AlphaDecCnt}
384
  \newcommand{\AlphaNo}{%
385
    \ifcase\number\theAlphaCnt
386
      \ifnum\c@chapter=0
387
        ZERO\else{}\fi
388
    \or ONE\or TWO\or THREE\or FOUR\or FIVE
389
    \or SIX\or SEVEN\or EIGHT\or NINE\or TEN
390
    \or ELEVEN\or TWELVE\or THIRTEEN\or FOURTEEN\or FIFTEEN
391
    \or SIXTEEN\or SEVENTEEN\or EIGHTEEN\or NINETEEN\fi
392
}
393

  
394
  \newcommand{\AlphaDecNo}{%
395
    \setcounter{AlphaDecCnt}{0}
396
    \@whilenum\number\theAlphaCnt>0\do
397
      {\addtocounter{AlphaCnt}{-10}
398
       \addtocounter{AlphaDecCnt}{1}}
399
     \ifnum\number\theAlphaCnt=0
400
     \else
401
       \addtocounter{AlphaDecCnt}{-1}
402
       \addtocounter{AlphaCnt}{10}
403
     \fi
404
     
405
     
406
    \ifcase\number\theAlphaDecCnt\or TEN\or TWENTY\or THIRTY\or
407
    FORTY\or FIFTY\or SIXTY\or SEVENTY\or EIGHTY\or NINETY\fi
408
    }
409
  \newcommand{\TheAlphaChapter}{%
410
    
411
    \ifinapp 
412
      \thechapter
413
    \else
414
      \setcounter{AlphaCnt}{\c@chapter}
415
      \ifnum\c@chapter<20
416
        \AlphaNo
417
      \else
418
        \AlphaDecNo\AlphaNo
419
      \fi
420
    \fi
421
    }  
422
  \renewcommand{\DOCH}{%
423
    \mghrulefill{\RW}\par\nobreak
424
    \CNV\FmN{\@chapapp}\par\nobreak 
425
    \CNoV\TheAlphaChapter\par\nobreak
426
    \vskip -1\baselineskip\vskip 5pt\mghrulefill{\RW}\par\nobreak
427
    \vskip 20\p@
428
    }
429
  \renewcommand{\DOTI}[1]{%
430
    \CTV\FmTi{#1}\par\nobreak
431
    \vskip 40\p@
432
    }
433
  \renewcommand{\DOTIS}[1]{%
434
    \CTV\FmTi{#1}\par\nobreak
435
    \vskip 40\p@
436
    }
437
}
438

  
439
\DeclareOption*{%
440
  \PackageWarning{fancychapter}{unknown style option}
441
  }
442

  
443
\ProcessOptions* \relax
444

  
445
\def\@makechapterhead#1{%
446
  \vspace*{50\p@}%
447
  {\parindent \z@ \raggedright \normalfont
448
    \ifnum \c@secnumdepth >\m@ne
449
      \if@mainmatter%%%%% Fix for frontmatter, mainmatter, and backmatter 040920
450
        \DOCH
451
      \fi
452
    \fi
453
    \interlinepenalty\@M
454
    \DOTI{#1}
455
  }}
456

  
457

  
458
%%% Begin: To avoid problem with scrbook.cls (fncychap version 1.32)
459

  
460
%%OUT:
461
%\def\@schapter#1{\if@twocolumn
462
%                   \@topnewpage[\@makeschapterhead{#1}]%
463
%                 \else
464
%                   \@makeschapterhead{#1}%
465
%                   \@afterheading
466
%                 \fi}
467

  
468
%%IN:
469
\def\@schapter#1{%
470
\if@twocolumn%
471
  \@makeschapterhead{#1}%
472
\else%
473
  \@makeschapterhead{#1}%
474
  \@afterheading%
475
\fi}
476

  
477
%%% End: To avoid problem with scrbook.cls (fncychap version 1.32)
478

  
479
\def\@makeschapterhead#1{%
480
  \vspace*{50\p@}%
481
  {\parindent \z@ \raggedright
482
    \normalfont
483
    \interlinepenalty\@M
484
    \DOTIS{#1}
485
    \vskip 40\p@
486
  }}
487

  
488
\endinput
489

  
490

  
doc/en/larpe-admin.rst
1
=====================================
2
Larpe - Administrator Guide
3
=====================================
4

  
5
:author: Damien Laniel
6
:contact: dlaniel@entrouvert.com
7
:copyright: Copyright © 2006 Entr'ouvert
8

  
9
.. contents:: Table of contents
10

  
11
Overview
12
========
13

  
14
Larpe is a Liberty Alliance Reverse Proxy. It allows any service provider
15
(that is a website) to use Liberty Alliance features (Identity federation,
16
Single Sign On and Single Logout) without changing the code of
17
the service provider itself. It uses the Lasso_ library
18
which is certified by the `Liberty Alliance`_ consortium. Lasso_ and Larpe
19
are released under the terms of the `GNU GPL license`_.
20

  
21

  
22
How to get and install Larpe
23
============================
24

  
25
Installation under Debian_ Sarge
26
++++++++++++++++++++++++++++++++
27

  
28
To work correctly Larpe relies on :
29

  
30
* Apache2_ ;
31

  
32
* Lasso_ (0.6.3) ;
33

  
34
* Quixote_ (2.0) ;
35

  
36
* SCGI_ ;
37

  
38
* mod_python_ ;
39

  
40
* libxml2 ;
41

  
42
* mod_proxy_html.
43

  
44
You will also need a Liberty Alliance Identity Provider, be it on the same server or not.
45
We recommend Authentic_ for that need.
46

  
47
Package Installation
48
--------------------
49

  
50
You need to add the following line to your /etc/apt/sources.list; this will
51
give you access to the repository where Larpe is stored::
52

  
53
 deb http://deb.entrouvert.org/ sarge main
54

  
55
As root type::
56

  
57
 apt-get update
58
 apt-get install larpe
59

  
60
And follow the debconf wizard to set it up.
61

  
62
All the required packages are now installed and configured.
63

  
64
You might need to change the "<VirtualHost \*>" in your apache2 configuration
65
(/etc/apache2/sites-available/apache2-vhost-larpe) depending on how you
66
previously configured apache.
67

  
68
Don't forget to modify your /etc/hosts file if necessary. Larpe now works, the
69
administration interface is reachable at http://your_domain_name/admin. The username
70
and password are the ones you entered during the installation wizard.
71

  
72
If you don't want to modify your sources.list file, you can manually dowload and
73
install the required packages with the dpkg -i command :
74

  
75
* Larpe, Authentic and Lasso on http://deb.entrouvert.org/ ;
76

  
77
* Quixote 2.0 on http://authentic.labs.libre-entreprise.org/.
78

  
79
Installation with another Linux distribution
80
++++++++++++++++++++++++++++++++++++++++++++
81

  
82
We suppose Apache2_, SCGI_, mod_python_, libxml2 and mod_proxy_html are already installed. You need then to
83
download and install the following sources :
84

  
85
* Lasso http://lasso.entrouvert.org ;
86

  
87
* Quixote http://www.mems-exchange.org/software/Quixote/ ;
88

  
89
* Authentic http://authentic.labs.libre-entreprise.org/ ;
90

  
91
* Larpe http://labs.libre-entreprise.org/frs/?group_id=108.
92

  
93
To install Larpe, uncompress the sources you have downloaded and launch the
94
setup.py script ::
95

  
96
 tar xzf larpe*.tar.gz
97
 cd larpe*
98
 python setup.py install
99

  
100
You need then to configure Apache2_ correctly. You should use the provided apache2-vhost-larpe template and adapt to your configuration.
101

  
102
Don't forget to modify your /etc/hosts file if necessary. Larpe now works, the
103
administration interface is reachable at http://your_domain_name/admin.
104

  
105
Basic Larpe configuration
106
=========================
107

  
108
Identity Provider configuration
109
+++++++++++++++++++++++++++++++
110

  
111
If you don't have a configured Identity Provider yet, please read Authentic
112
manual to set it up. Then you must have the metadata and public key of the Identity
113
Provider to begin with Larpe.
114

  
115
Then in Larpe administration interface, click on "Settings", then "Identity Provider".
116
Fill in the metadata and public key that you've got from your Identity Provider then
117
click Submit.
118
Your Identity Provider is now configured in Larpe, you can then configure as many Service
119
Providers as you want.
120

  
121
Service Provider Configuration
122
++++++++++++++++++++++++++++++
123

  
124
Service Provider configuration
125
------------------------------
126

  
127
Click on "Hosts" then "New Host".
128

  
129
Fill in the following parameters :
130

  
131
* Label : the name you want to give to your Service Provider ;
132

  
133
* Original Site Address : the root URL of your Service Provider ;
134

  
135
* Authentication Page : if the page which contains the authentication form for
136
  your Service Provider is on a separate page, fill the url of this page here ;
137

  
138
* Authentication Form Page : if you didn't fill the previous field and if the
139
  authentication form if not on the first page of your Service Provider either,
140
  fill the url of the page which contains the authentication form here ;
141

  
142
* Logout Address : when you want Single Sign On and Identity Federation, you probably
143
  want Single Logout too. If so, fill the logout url of your original site here ;
144

  
145
* Reversed Host Name : the domain name where you want to access your Service Provider
146
  through the reverse proxy. It can be the domain name of Larpe or not ;
147

  
148
Then click "Submit". Wait a few seconds then go to http://reversed_host_name/reverse_directory/
149
to check if it works. If not, wait a bit more and try again. If it really doesn't work,
150
please submit a bug report at http://labs.libre-entreprise.org/tracker/?func=add&group_id=108&atid=512
151

  
152
Service Provider Example: Linuxfr
153
---------------------------------
154

  
155
To help you setup your own Service Provider, we provide an example of a working Service Provider
156
to guide you.
157

  
158
To setup Linuxfr, fill in the following parameters :
159

  
160
* Label : Linuxfr ;
161

  
162
* Original Site Address : http://linuxfr.org/ ;
163

  
164
* Authentication Page : Nothing here ;
165

  
166
* Authentication Form Page : http://linuxfr.org/pub/ ;
167

  
168
* Logout Address : http://linuxfr.org/close_session.html ;
169

  
170
* Reversed Host Name : linuxfr.reverse-proxy.example.com.
171

  
172
With "reverse-proxy.example.com" being the hostname you've set up before for your reverse-proxy
173

  
174
Don't forget to add this new hostname to your /etc/hosts as well.
175

  
176
You can then go to the reversed Linuxfr at http://linuxfr.reverse-proxy.example.com/
177

  
178
Service Provider Liberty Alliance final setup
179
---------------------------------------------
180

  
181
Now that you can access your Service Provider, you need a final step to use Liberty Alliance
182
features. Click on "Hosts", the click on the "Edit" icon of the Service Provider you've
183
just configured. Save the Service Provider Metadata (for ID-FF 1.2) and the Public Key
184
(right click then "Save as"). Configure this Service Provider on your Identity Provider
185
with these two files.
186

  
187
Licenses
188
========
189

  
190
Larpe, Authentic_, Candle_ and Lasso_ are released under the terms of the
191
`GNU GPL license`_.
192

  
193
.. _Lasso: http://lasso.entrouvert.org/
194
.. _`Liberty Alliance`: http://projectliberty.org/
195
.. _`GNU GPL License`: http://www.gnu.org/copyleft/gpl.html
196
.. _Debian: http://www.debian.org/
197
.. _Apache2: http://httpd.apache.org/
198
.. _Quixote: http://www.mems-exchange.org/software/Quixote
199
.. _mod_python: http://www.modpython.org/
200
.. _SCGI: http://www.mems-exchange.org/software/scgi/
201
.. _Candle: http://candle.labs.libre-entreprise.org/
202
.. _Authentic: http://www.entrouvert.com/fr/authentic/
doc/scripts/removealpha.sh
1
#! /bin/sh
2

  
3
size=$(identify $1 | cut -d ' ' -f 3)
4
composite $1 -size $(identify $1 | cut -d ' ' -f3) xc:white $2
5

  
doc/scripts/rst2latex.py
1
#! /usr/bin/python
2

  
3
"""A minimal reST frontend, to create appropriate LaTeX files."""
4

  
5
try:
6
    import locale
7
    locale.setlocale(locale.LC_ALL, '')
8
except:
9
    pass
10

  
11
from docutils.core import publish_cmdline, Publisher
12

  
13
def set_io(self, source_path=None, destination_path=None):
14
    Publisher.set_io_orig(self, source_path, destination_path='/dev/null')
15

  
16
Publisher.set_io_orig, Publisher.set_io = Publisher.set_io, set_io
17

  
18
output = publish_cmdline(writer_name='latex',
19
    settings_overrides = {
20
        'documentclass': 'report',
21
        'documentoptions': '11pt,a4paper,titlepage',
22
        'use_latex_toc': True,
23
        'use_latex_docinfo': True,
24
        'stylesheet': 'custom.tex'})
25

  
26
output = output.replace('\\includegraphics',
27
    '\\includegraphics[width=.9\\textwidth,height=15cm,clip,keepaspectratio]')
28
output = output.replace('\\begin{figure}[htbp]', '\\begin{figure}[H]')
29
print output
exclude_from_dist
1
.svn
2
*.pyc
3
*.pyo
4
*.pye
5
*.ptle
6
*.swp
7
debian.sarge
8
make_debian_package.sh
9
build
10
dist
11
tests
12
larpe/filter
fedora/larpe-reload-apache2-script
1
#!/bin/sh
2
#
3
# The command "/etc/init.d/httpd reload" on Fedora actually _restarts_ Apache
4
# We need to _reload_ it without closing existing connections
5

  
6
APACHE2CTL=/usr/sbin/apachectl
7

  
8
echo -n "Testing Apache config... "
9
if ! $APACHE2CTL configtest > /dev/null 2>&1; then
10
	$APACHE2CTL configtest || true
11
	echo "[FAILED]"
12
	exit 1
13
else
14
	echo "[OK]"
15
fi
16
echo -n "Reloading Apache config... "
17
if $APACHE2CTL graceful $2 ; then
18
	echo "[OK]"
19
else
20
	echo "[FAILED]"
21
fi
22

  
fedora/larpe.init
1
#! /bin/bash
2
#
3
# larpe        Startup script for the Larpe reverse proxy
4
#
5
# description: Larpe is Liberty Alliance reverse proxy.  It is used to add Liberty Alliance \
6
#           features to some sites without modifying the sites themselves.
7
# processname: larpe
8
# config: /etc/httpd/conf.d/larpe.conf
9
# config: /etc/larpe/apache2-vhost-larpe-common
10
# config: /etc/sysconfig/larpe
11
# pidfile: /var/run/larpe.pid
12
#
13

  
14
### BEGIN INIT INFO
15
# Provides:          larpe
16
# Required-Start:    $local_fs $network
17
# Required-Stop:     $local_fs $network
18
# Default-Start:
19
# Default-Stop:      0 1 2 3 4 5 6
20
# Short-Description: Start Larpe Liberty Alliance reverse proxy
21
# Description:       Start Larpe Liberty Alliance reverse proxy
22
### END INIT INFO
23

  
24
prog=larpe
25
LARPECTL=/usr/sbin/larpectl
26
PIDFILE=/var/run/larpe.pid
27

  
28
# Source function library.
29
. /etc/rc.d/init.d/functions
30

  
31
# Source networking configuration.
32
. /etc/sysconfig/network
33

  
34
# Check that networking is up.
35
[ ${NETWORKING} = "no" ] && exit 0
36

  
37
[ -x $LARPECTL ] || exit 5
38

  
39
# Read config file if it is present.
40
if [ -f /etc/sysconfig/larpe ]; then
41
	. /etc/sysconfig/larpe
42
fi
43

  
44
RETVAL=0
45

  
46
#
47
# Function that starts the daemon/service.
48
#
49
start() {
50
	echo -n $"Starting $prog: "
51
	$LARPECTL start &
52
	RETVAL=$?
53
	if [ $RETVAL -eq 0 ]
54
	then
55
		touch /var/lock/subsys/$prog
56
	fi
57
	echo
58
	return $RETVAL
59
}
60

  
61
#
62
# Function that stops the daemon/service.
63
#
64
stop() {
65
	echo -n $"Stopping $prog: "
66
	killproc $LARPECTL
67
	RETVAL=$?
68
	if [ $RETVAL -eq 0 ]
69
	then
70
		rm -rf /var/lock/subsys/$prog
71
	fi
72
	echo
73
	return $RETVAL
74
}
75

  
76
# See how we were called.
77
case "$1" in
78
	start)
79
		start
80
	;;
81
	stop)
82
		stop
83
	;;
84
	status)
85
		status $prog
86
		RETVAL=$?
87
	;;
88
	restart)
89
		stop
90
		start
91
	;;
92
	condrestart)
93
		if [ -f ${PIDFILE} ] ; then
94
			stop
95
			start
96
		fi
97
	;;
98
	*)
99
		echo $"Usage: $prog {start|stop|restart|condrestart|status}"
100
		exit 1
101
esac
102

  
103
exit $RETVAL
104

  
fedora/larpe.spec
1
%{!?python_sitearch: %define python_sitearch %(%{__python} -c 'from distutils import sysconfig; print sysconfig.get_python_lib(1)')}
2
# eval to 2.3 if python isn't yet present, workaround for no python in fc4 minimal buildroot
3
%{!?python_version: %define python_version %(%{__python} -c 'import sys; print sys.version.split(" ")[0]' || echo "2.3")}
4
%define apacheconfdir   %{_sysconfdir}/httpd/conf.d
5

  
6
Summary: Liberty Alliance Reverse Proxy
7
Name: larpe
8
Version: 0.2.9
9
Release: 2%{?dist}
10
License: GPL
11
Group: System Environment/Applications
12
Url: http://larpe.labs.libre-entreprise.org/
13
Source0: http://labs.libre-entreprise.org/frs/download.php/591/%{name}-%{version}.tar.gz
14
Buildroot: %{_tmppath}/%{name}-%{version}-%(id -u -n)
15
BuildRequires: python >= 2.3, python-quixote >= 2.0
16
BuildRequires: gettext
17
Requires: httpd >= 2.0, mod_scgi, mod_proxy_html
18
Requires: lasso-python >= 0.6.3, python-quixote >= 2.0, python-scgi
19
Requires: initscripts
20
Requires(post): /sbin/chkconfig
21
Requires(preun):/sbin/chkconfig
22
Requires(preun): /sbin/service
23

  
24
%description
25
Larpe is a Liberty Alliance Reverse Proxy. It allows any service provider (that is a website)
26
to use Liberty Alliance features (Identity federation, Single Sign On and Single Logout) without
27
changing the code of the service provider itself.
28

  
29
It uses the Lasso library which is certified by the Liberty Alliance consortium.
30

  
31
It is a quixote application and is commonly runned inside Apache web server.
32

  
33
%package doc
34
Summary: Documentation files for %{name} development.
35
Group: Documentation
36
BuildRequires: python-docutils, tetex-latex
37

  
38
%description doc
39
This package contains development documentation for %{name}.
40

  
41
%prep
42
%setup -q
43

  
44
# Change Apache vhost path in Larpe config
45
sed -i s#"/var/log/apache2/larpe-access.log"#"logs/larpe_access_log combined\n    TransferLog logs/larpe_access_log"# conf/apache2-vhost-larpe
46
sed -i s#"/var/log/apache2/larpe-error.log"#"logs/larpe_error_log"# conf/apache2-vhost-larpe
47
sed -i s#"APACHE_MAIN_VHOST.*$"#"APACHE_MAIN_VHOST='/etc/httpd/conf.d/larpe.conf'"# larpe/Defaults.py
48

  
49
%build
50

  
51
%install
52
rm -rf %{buildroot}
53

  
54
# install generic files
55
make install prefix=%{_prefix} DESTDIR=%{buildroot}
56

  
57
# install init script
58
install -d %{buildroot}/%{_initrddir}
59
install -p -m 0755 fedora/larpe.init %{buildroot}%{_initrddir}/larpe
60

  
61
# apache configuration
62
mkdir -p %{buildroot}%{apacheconfdir}
63
install -p -m 644 conf/apache2-vhost-larpe %{buildroot}%{apacheconfdir}/larpe.conf
64

  
65
# apache reload script
66
install -p -m 0755 fedora/larpe-reload-apache2-script %{buildroot}%{_sbindir}/
67

  
68
# install doc files
69
install -d -m 0755 %{buildroot}%{_datadir}/gtk-doc/html/larpe
70
make -C doc DESTDIR=%{buildroot}%{_datadir}/gtk-doc/html/larpe
71

  
72
%clean
73
rm -fr %{buildroot}
74

  
75
%post
76
/sbin/chkconfig --add %{name}
77

  
78
# manual post-installation
79
cat <<_EOF_
80
You must edit first %{apacheconfdir}/larpe.conf
81

  
82
You must enable Larpe with "chkconfig larpe on ; service larpe start"
83

  
84
You must also restart Apache with "service httpd restart"!
85
_EOF_
86

  
87
%preun
88
if [ $1 = 0 ]; then
89
	/sbin/service %{name} stop > /dev/null 2>&1
90
	/sbin/chkconfig --del %{name}
91
fi
92

  
93
%files
94
%defattr(-,root,root,755)
95
%config %{_initrddir}/larpe
96
%config(noreplace) %{apacheconfdir}/larpe.conf
97
%config(noreplace) %{_sysconfdir}/larpe/apache2-vhost-larpe-common
98
%{_sbindir}/larpectl
99
%{_sbindir}/larpe-reload-apache2
100
%{_sbindir}/larpe-reload-apache2-script
101
%{python_sitearch}/%{name}
102
%{_datadir}/%{name}
103
%{_datadir}/locale/fr/LC_MESSAGES/larpe.mo
104
/var/lib/larpe
105
%defattr(644,root,root,755)
106
%doc AUTHORS COPYING NEWS README
107

  
108
%files doc
109
%defattr(-,root,root)
110
%doc %{_datadir}/gtk-doc/html/%{name}
111

  
112
%changelog
113
* Tue Mar 05 2009 Jean-Marc Liger <jmliger@siris.sorbonne.fr> 0.2.9-2
114
- Added missing BuildRequires gettext
115
- Enabled larpe init script
116

  
117
* Mon Jan 19 2009 Damien Laniel <dlaniel@entrouvert.com> 0.2.9-1
118
- Updated to 0.2.9
119
- Use Larpe Makefile to install generic files
120
- Copy fedora specific files
121

  
122
* Sat Jan 17 2009 Jean-Marc Liger <jmliger@siris.sorbonne.fr> 0.2.1-2
123
- Added missing BuildRequires tetex-latex for doc subpackage
124
- Rebuilt on CentOS 4,5
125

  
126
* Wed Jan 14 2009 Jean-Marc Liger <jmliger@siris.sorbonne.fr> 0.2.1-1
127
- Updated to 0.2.1
128
- Added missing Requires lasso-python
129
- Added missing Requires python-scgi
130
- Rebuilt on CentOS 4,5
131

  
132
* Fri Mar 02 2007 Jean-Marc Liger <jmliger@siris.sorbonne.fr> 0.2.0-1
133
- Updated to 0.2.0
134
- Added BuildRequires python-quixote
135
- Built on Fedora Core 3 / RHEL 4 and Fedora Core 6 / RHEL 5
136

  
137
* Wed Jan 24 2007 Jean-Marc Liger <jmliger@siris.sorbonne.fr> 0.1.0-1
138
- First 0.1.0
139
- Built on Fedora Core 3 / RHEL 4 and Fedora Core 6 / RHEL 5
larpe-reload-apache2.c
1
/*
2
  Template for a setuid program that calls a script.
3

  
4
  The script should be in an unwritable directory and should itself
5
  be unwritable.  In fact all parent directories up to the root
6
  should be unwritable.  The script must not be setuid, that's what
7
  this program is for.
8

  
9
  This is a template program.  You need to fill in the name of the
10
  script that must be executed.  This is done by changing the
11
  definition of FULL_PATH below.
12

  
13
  There are also some rules that should be adhered to when writing
14
  the script itself.
15

  
16
  The first and most important rule is to never, ever trust that the
17
  user of the program will behave properly.  Program defensively.
18
  Check your arguments for reasonableness.  If the user is allowed to
19
  create files, check the names of the files.  If the program depends
20
  on argv[0] for the action it should perform, check it.
21

  
22
  Assuming the script is a Bourne shell script, the first line of the
23
  script should be
24
  #!/bin/sh -
25
  The - is important, don't omit it.  If you're using esh, the first
26
  line should be
27
  #!/usr/local/bin/esh -f
28
  and for ksh, the first line should be
29
  #!/usr/local/bin/ksh -p
30
  The script should then set the variable IFS to the string
31
  consisting of <space>, <tab>, and <newline>.  After this (*not*
32
  before!), the PATH variable should be set to a reasonable value and
33
  exported.  Do not expect the PATH to have a reasonable value, so do
34
  not trust the old value of PATH.  You should then set the umask of
35
  the program by calling
36
  umask 077 # or 022 if you want the files to be readable
37
  If you plan to change directories, you should either unset CDPATH
38
  or set it to a good value.  Setting CDPATH to just ``.'' (dot) is a
39
  good idea.
40
  If, for some reason, you want to use csh, the first line should be
41
  #!/bin/csh -fb
42
  You should then set the path variable to something reasonable,
43
  without trusting the inherited path.  Here too, you should set the
44
  umask using the command
45
  umask 077 # or 022 if you want the files to be readable
46
*/
47

  
48
#include <unistd.h>
49
#include <stdlib.h>
50
#include <stdio.h>
51
#include <sys/types.h>
52
#include <sys/stat.h>
53
#include <string.h>
54

  
55
/* CONFIGURATION SECTION */
56

  
57
#ifndef FULL_PATH/* so that this can be specified from the Makefile */
58
  #define FULL_PATH	"/usr/sbin/larpe-reload-apache2-script"
59
#endif
60
#ifndef UMASK
61
  #define UMASK		077
62
#endif
63

  
64
/* END OF CONFIGURATION SECTION */
65

  
66
#if defined(__STDC__) && defined(__sgi)
67
#define environ _environ
68
#endif
69

  
70
/* don't change def_IFS */
71
char def_IFS[] = "IFS= \t\n";
72
/* you may want to change def_PATH, but you should really change it in */
73
/* your script */
74
#ifdef __sgi
75
char def_PATH[] = "PATH=/usr/bsd:/usr/bin:/bin:/usr/local/bin:/usr/sbin";
76
#else
77
char def_PATH[] = "PATH=/usr/ucb:/usr/bin:/bin:/usr/local/bin";
78
#endif
79
/* don't change def_CDPATH */
80
char def_CDPATH[] = "CDPATH=.";
81
/* don't change def_ENV */
82
char def_ENV[] = "ENV=:";
83

  
84
/*
85
  This function changes all environment variables that start with LD_
86
  into variables that start with XD_.  This is important since we
87
  don't want the script that is executed to use any funny shared
88
  libraries.
89

  
90
  The other changes to the environment are, strictly speaking, not
91
  needed here.  They can safely be done in the script.  They are done
92
  here because we don't trust the script writer (just like the script
93
  writer shouldn't trust the user of the script).
94
  If IFS is set in the environment, set it to space,tab,newline.
95
  If CDPATH is set in the environment, set it to ``.''.
96
  Set PATH to a reasonable default.
97
*/
98
void
99
clean_environ(void)
100
{
101
    char **p;
102
    extern char **environ;
103

  
104
    for (p = environ; *p; p++) {
105
	if (strncmp(*p, "LD_", 3) == 0)
106
	    **p = 'X';
107
	else if (strncmp(*p, "_RLD", 4) == 0)
108
	    **p = 'X';
109
	else if (strncmp(*p, "PYTHON", 6) == 0)
110
	    **p = 'X';
111
	else if (strncmp(*p, "IFS=", 4) == 0)
112
	    *p = def_IFS;
113
	else if (strncmp(*p, "CDPATH=", 7) == 0)
114
	    *p = def_CDPATH;
115
	else if (strncmp(*p, "ENV=", 4) == 0)
116
	    *p = def_ENV;
117
    }
118
    putenv(def_PATH);
119
}
120

  
121
int
122
main(int argc, char **argv)
123
{
124
    struct stat statb;
125
    gid_t egid = getegid();
126
    uid_t euid = geteuid();
127

  
128
    /*
129
      Sanity check #1.
130
      This check should be made compile-time, but that's not possible.
131
      If you're sure that you specified a full path name for FULL_PATH,
132
      you can omit this check.
133
    */
134
    if (FULL_PATH[0] != '/') {
135
	fprintf(stderr, "%s: %s is not a full path name\n", argv[0],
136
		FULL_PATH);
137
	fprintf(stderr, "You can only use this wrapper if you\n");
138
	fprintf(stderr, "compile it with an absolute path.\n");
139
	exit(1);
140
    }
141

  
142
    /*
143
      Sanity check #2.
144
      Check that the owner of the script is equal to either the
145
      effective uid or the super user.
146
    */
147
    if (stat(FULL_PATH, &statb) < 0) {
148
	perror("stat");
149
	exit(1);
150
    }
151
    if (statb.st_uid != 0 && statb.st_uid != euid) {
152
	fprintf(stderr, "%s: %s has the wrong owner\n", argv[0],
153
		FULL_PATH);
154
	fprintf(stderr, "The script should be owned by root,\n");
155
	fprintf(stderr, "and shouldn't be writeable by anyone.\n");
156
	exit(1);
157
    }
158

  
159
    if (setregid(egid, egid) < 0)
160
	perror("setregid");
161
    if (setreuid(euid, euid) < 0)
162
	perror("setreuid");
163

  
164
    clean_environ();
165

  
166
    umask(UMASK);
167

  
168
    while (**argv == '-')/* don't let argv[0] start with '-' */
169
	(*argv)++;
170
    execv(FULL_PATH, argv);
171
    fprintf(stderr, "%s: could not execute the script\n", argv[0]);
172
    exit(1);
173
}
larpe/Defaults.py
1
'''Default global variables'''
2

  
3
APP_DIR = '/var/lib/larpe'
4
DATA_DIR = '/usr/share/larpe'
5
ERROR_LOG = None #'/var/log/larpe.log'
6
WEB_ROOT = 'larpe'
7
APACHE_MAIN_VHOST = '/etc/apache2/sites-available/apache2-vhost-larpe'
8
APACHE_VHOST_COMMON = '/etc/larpe/apache2-vhost-larpe-common'
9
APACHE_RELOAD = '/usr/sbin/larpe-reload-apache2'
larpe/__init__.py
1
'''Setup path and load Lasso library'''
2
try:
3
    from quixote.ptl import compile_package
4
    compile_package(__path__)
5
except ImportError:
6
    pass
7

  
8
import sys
9
import os
10
sys.path.insert(0, os.path.dirname(__file__))
11

  
12
import qommon
13

  
14
try:
15
    import lasso
16
except ImportError:
17
    lasso = None
18

  
19
if lasso and not hasattr(lasso, 'SAML2_SUPPORT'):
20
    lasso.SAML2_SUPPORT = False
21

  
larpe/admin/__init__.py
1
try:
2
    from quixote.ptl import compile_package
3
    compile_package(__path__)
4
except ImportError:
5
    pass
6
from root import RootDirectory
larpe/admin/apache.py
1
import os
2
import re
3
import urllib
4
import base64
5

  
6
from quixote import get_publisher, get_request
7

  
8
from qommon import get_cfg
9

  
10
from larpe.hosts import Host
11
from larpe.Defaults import APP_DIR, APACHE_MAIN_VHOST, APACHE_VHOST_COMMON, APACHE_RELOAD
12

  
13
def write_apache2_vhosts():
14
    hosts = Host.select(lambda x: x.name != 'larpe')
15
    hosts.sort()
16
    vhosts_dir = os.path.join(APP_DIR, 'vhosts.d')
17
    vhost_locations_dir = os.path.join(APP_DIR, 'vhost-locations.d')
18
    vhosts_dir_disabled = os.path.join(APP_DIR, 'vhosts.d.disabled')
19
    vhost_locations_dir_disabled = os.path.join(APP_DIR, 'vhost-locations.d.disabled')
20
    vhost_file_name = get_request().get_server().split(':')[0]
21
    vhost_file = None
22
    reversed_hostname = ''
23
    vhost = None
24

  
25
    if get_publisher().cfg.get(str('allow_config_generation'), True):
26
        vhost_file = open(os.path.join(vhosts_dir, vhost_file_name), 'w')
27
        locations_file = open(os.path.join(vhost_locations_dir, vhost_file_name), 'w')
28
    else:
29
        vhost_file = open(os.path.join(vhosts_dir_disabled, vhost_file_name), 'w')
30
        locations_file = open(os.path.join(vhost_locations_dir_disabled, vhost_file_name), 'w')
31
    try:
32
        main_vhost = open(APACHE_MAIN_VHOST, 'r')
33
        file_content = main_vhost.read()
34
        regexp = re.compile('<VirtualHost (.*?)>')
35
        vhost_ip = regexp.findall(file_content)[0]
36
    except (IOError, IndexError):
37
        vhost_ip = '*'
38

  
39
    for host in hosts:
40
        if host.orig_site is None:
41
            # This site hasn't been fully configured
42
            continue
43
        if host.reversed_hostname != reversed_hostname \
44
                and host.reversed_hostname != get_cfg('proxy_hostname'):
45
            if vhost is not None:
46
                vhost.close()
47
            vhost = Vhost(host, vhost_ip)
48
            vhost.write(vhost_file)
49
            reversed_hostname = host.reversed_hostname
50

  
51
        if host.reversed_hostname == get_cfg('proxy_hostname'):
52
            conf_file = locations_file
53
        else:
54
            conf_file = vhost_file
55

  
56
        Location(host).write(conf_file)
57

  
58
    if vhost_file is not None:
59
        if vhost is not None:
60
            vhost.close()
61
        vhost_file.close()
62
    if locations_file is not None:
63
        locations_file.close()
64

  
65
    if get_publisher().cfg.get(str('allow_config_generation'), True):
66
        os.system(APACHE_RELOAD)
67

  
68

  
69
class Vhost(object):
70
    def __init__(self, host, main_ip_port):
71
        self.host = host
72
        self.main_ip_port = main_ip_port
73
        self.conf_file = None
74

  
75
    def get_ip_port(self):
76
        if self.host.scheme == 'https':
77
            return self.main_ip_port.replace(':80', ':443')
78
        else:
79
            return self.main_ip_port.replace(':443', ':80')
80
    ip_port = property(get_ip_port)
81

  
82
    def get_proxy_url(self):
83
        if get_cfg('proxy', {}).get('enabled') and self.host.use_proxy == True:
84
            return 'http://%(ip)s:%(port)s' % get_cfg('proxy', {})
85
        return None
86
    proxy_url = property(get_proxy_url)
87

  
88
    def get_proxy_auth(self):
89
        if self.get_proxy_url() and get_cfg('proxy', {}).get('user'):
90
            credentials = base64.encodestring(
91
                '%(user)s:%(password)s' % get_cfg('proxy', {}))[:-1]
92
            return '"Basic %s"' % credentials
93
        return None
94
    proxy_auth = property(get_proxy_auth)
95

  
96
    def get_cfg(self):
97
        return { 'ip_port': self.ip_port,
98
                 'reversed_hostname': self.host.reversed_hostname,
99
                 'proxy_url': self.proxy_url,
100
                 'proxy_auth': self.proxy_auth }
101
    cfg = property(get_cfg)
102

  
103
    def write(self, conf_file):
104
        self.conf_file = conf_file
105
        conf_lines = []
106
        # Start Virtual Host
107
        conf_lines.append('<VirtualHost %(ip_port)s>' % self.cfg)
108
        # Server name and administrator
109
        conf_lines.append('ServerName %(reversed_hostname)s' % self.cfg)
110
        conf_lines.append('# ServerAdmin root@localhost\n')
111
        # Include common vhost configuration
112
        conf_lines.append('include %s\n' % APACHE_VHOST_COMMON)
113
        # SSL
114
        if self.host.scheme == 'https':
115
            conf_lines.append('SSLEngine On\n')
116
        if self.host.orig_site.startswith('https'):
117
            conf_lines.append('SSLProxyEngine On\n')
118
        # Remote proxy configuration
119
        if self.proxy_url is not None:
120
            conf_lines.append('ProxyRemote * %(proxy_url)s' % self.cfg)
121
            if self.proxy_auth is not None:
122
                conf_lines.append(
123
                    'RequestHeader set Proxy-Authorization %(proxy_auth)s\n' % self.cfg)
124
        # Write it all
125
        conf_file.write('\n\t'.join(conf_lines))
126

  
127
    def close(self):
128
        if self.conf_file:
129
            self.conf_file.write('</VirtualHost>\n\n')
130

  
131

  
132
def apache_escape_chars(url):
133
    special_characters = ('\\', '.', '?', '*', '+', '^', '$', '|', '(', ')', '[', ']')
134
    for char in special_characters:
135
        url = url.replace(char, '\%s' % char)
136
    return url
137

  
138
class Location(object):
139
    def __init__(self, host):
140
        self.host = host
141

  
142
    def get_reversed_directory(self):
143
        if not self.host.reversed_directory:
144
            return '%s/' % get_request().environ['SCRIPT_NAME']
145
        else:
146
            return '%s/%s/' % (get_request().environ['SCRIPT_NAME'], self.host.reversed_directory)
147
    reversed_directory = property(get_reversed_directory)
148

  
149
    def get_python_path(self):
150
        if self.host.apache_output_python_filters and \
151
                self.host.apache_python_paths:
152
            python_path = 'PythonPath "sys.path'
153
            for path in self.host.apache_python_paths:
154
                python_path += "+['%s']" % path
155
            python_path += '"'
156
            return python_path
157
        else:
158
            return None
159
    python_path = property(get_python_path)
160

  
161
    def get_output_filters(self):
162
        python_filters = ''
163
        output_filters = []
164
        if self.host.apache_output_python_filters:
165
            i = 0
166
            for filter_file in self.host.apache_output_python_filters:
167
                filter_name = 'filter%d' % i
168
                python_filters += 'PythonOutputFilter %s %s\n\t\t' % (filter_file, filter_name)
169
                output_filters.append(filter_name)
170
                i += 1
171
        if self.host.apache_output_filters:
172
            for output_filter in self.host.apache_output_filters:
173
                output_filters.append(output_filter)
174
        if output_filters:
175
            return python_filters + 'SetOutputFilter ' + ';'.join(output_filters)
176
        else:
177
            return None
178
    output_filters = property(get_output_filters)
179

  
180
    def get_old_auth_url(self):
181
        old_auth_url = None
182
        if self.host.initiate_sso_url:
183
            old_auth_url = self.host.initiate_sso_url
184
        elif self.host.auth_url is not None:
185
            if self.host.auth_url.startswith('http://'):
186
                chars_to_skip = 5
187
            else:
188
                chars_to_skip = 6
189
            regexp = re.compile(self.host.orig_site[chars_to_skip:])
190
            old_auth_url_short = regexp.sub('', self.host.auth_url[chars_to_skip:])
191
            if old_auth_url_short.startswith('/'):
192
                old_auth_url = old_auth_url_short
193
            else:
194
                old_auth_url = '/' + old_auth_url_short
195
        if old_auth_url:
196
            old_auth_url = apache_escape_chars(old_auth_url)
197
        return old_auth_url
198
    old_auth_url = property(get_old_auth_url)
199

  
200
    def get_new_auth_url(self):
201
        if not hasattr(self.host, 'base_url'):
202
            return None
203
        base_url_tokens = self.host.base_url.split('/')
204
        base_url_tokens[-1] = 'login'
205
        return '/'.join(base_url_tokens)
206
    new_auth_url = property(get_new_auth_url)
207

  
208
    def get_old_logout_url(self):
209
        old_logout_url = None
210
        if self.host.logout_url is not None:
211
            if self.host.logout_url.startswith('http://'):
212
                chars_to_skip = 5
213
            else:
214
                chars_to_skip = 6
215
            regexp = re.compile(self.host.orig_site[chars_to_skip:])
216
            old_logout_url_short = regexp.sub('', self.host.logout_url[chars_to_skip:])
217
            if old_logout_url_short.startswith('/'):
218
                old_logout_url = old_logout_url_short
219
            else:
220
                old_logout_url = '/' + old_logout_url_short
221
            old_logout_url = apache_escape_chars(old_logout_url)
222
        return old_logout_url
223
    old_logout_url = property(get_old_logout_url)
224

  
225
    def get_new_logout_url(self):
226
        if not hasattr(self.host, 'base_url'):
227
            return None
228
        base_url_tokens = self.host.base_url.split('/')
229
        base_url_tokens[-1] = 'logout'
230
        return '/'.join(base_url_tokens)
231
    new_logout_url = property(get_new_logout_url)
232

  
233
    def get_orig_site_url_and_dir(self):
234
        # Split url
235
        if self.host.orig_site.startswith('http://'):
236
            orig_host, orig_query = urllib.splithost(self.host.orig_site[5:])
237
        else:
238
            orig_host, orig_query = urllib.splithost(self.host.orig_site[6:])
239
        # Add a trailing slash if necessary
240
        if self.host.orig_site.endswith('/'):
241
            orig_url = self.host.orig_site
242
            orig_dir = orig_query
243
        else:
244
            orig_url = self.host.orig_site + '/'
245
            orig_dir = orig_query + '/'
246
        return orig_url, orig_dir
247

  
248
    def get_orig_url(self):
249
        orig_url, orig_dir = self.get_orig_site_url_and_dir()
250
        return orig_url
251
    orig_url = property(get_orig_url)
252

  
253
    def get_orig_dir(self):
254
        orig_url, orig_dir = self.get_orig_site_url_and_dir()
255
        return orig_dir
256
    orig_dir = property(get_orig_dir)
257

  
258
    def get_cfg(self):
259
        return { 'reversed_directory': self.reversed_directory,
260
                 'old_auth_url': self.old_auth_url,
261
                 'new_auth_url': self.new_auth_url,
262
                 'old_logout_url': self.old_logout_url,
263
                 'new_logout_url': self.new_logout_url,
264
                 'orig_url': self.orig_url,
265
                 'orig_dir': self.orig_dir }
266
    cfg = property(get_cfg)
267

  
268
    def write(self, conf_file):
269
        conf_lines = []
270
        # Start Location
271
        conf_lines.append('\n\t<Location %(reversed_directory)s>' % self.cfg)
272
        # No user restriction
273
        conf_lines.append('Allow from all')
274
        # Apache output filters
275
        if self.python_path:
276
            conf_lines.append(self.python_path)
277
        if self.output_filters:
278
            conf_lines.append(self.output_filters)
279
        # Redirect rules
280
        # Redirect old authentication url to the new one
281
        if self.old_auth_url is not None and self.host.auth_form_places == 'form_once':
282
            conf_lines.append(
283
                'RedirectMatch         %(old_auth_url)s    %(new_auth_url)s' % self.cfg)
284
        # Redirect old logout url to the new one
285
        if self.old_logout_url is not None:
286
            conf_lines.append(
287
                'RedirectMatch         %(old_logout_url)s    %(new_logout_url)s' % self.cfg)
288
        # Redirect the home page to the login page
289
        if self.host.redirect_root_to_login is True:
290
            conf_lines.append('RedirectMatch        ^/$      %(new_auth_url)s' % self.cfg)
291
        # Convert urls in http headers to/from the new domain
292
        conf_lines.append('ProxyPass           %(orig_url)s' % self.cfg)
293
        conf_lines.append('ProxyPassReverse    %(orig_url)s' % self.cfg)
294
        # Convert urls in html pages to/from the new domain
295
        conf_lines.append('ProxyHTMLURLMap     %(orig_dir)s     %(reversed_directory)s' % self.cfg)
296
        conf_lines.append('ProxyHTMLURLMap     %(orig_url)s    %(reversed_directory)s' % self.cfg)
297
        # Write it all and close the Location
298
        conf_file.write('\n\t\t'.join(conf_lines))
299
        conf_file.write('\n\t</Location>\n')
300

  
larpe/admin/fields_prefill.ptl
1
from quixote import get_response, redirect
2
from quixote.directory import Directory
3

  
4
from qommon.form import *
5
from qommon.admin.menu import html_top, command_icon
6

  
7
from field_prefill import FieldPrefill
8

  
9
class FieldUI:
10
    def __init__(self, field_prefill):
11
        self.field_prefill = field_prefill
12

  
13
    def form_edit(self):
14
        form = Form(enctype='multipart/form-data')
15
        form.add(StringWidget, 'name', title = _('Field name'), required = True,
16
            size = 50, value = self.field_prefill.name)
17
        form.add(StringWidget, 'xpath', title = _('Xpath of the attribute'), required = True,
18
            size = 50, value = self.field_prefill.xpath, hint=_('Example: /pp:PP/pp:InformalName'))
19
        form.add(IntWidget, 'number', title = _('Number of the field in the data'), required = True,
20
            size = 3, value = self.field_prefill.number,
21
            hint=_('Change it if there are multiple fields corresponding to the same Xpath and you want to get another than the first one'))
22
        form.add(CheckboxWidget, 'raw_xml', title=_('Get raw XML value'),
23
            value = self.field_prefill.raw_xml)
24
        form.add(StringWidget, 'regexp_match', title = _('Python regexp of a string to match'),
25
            size = 50, value = self.field_prefill.regexp_match)
26
        form.add(StringWidget, 'regexp_replacing', title = _('Python regexp of the replacing string'),
27
            size = 50, value = self.field_prefill.regexp_replacing)
28
        form.add(WidgetDict, 'select_options', title = _('Options mapping for a select field'),
29
                value = self.field_prefill.select_options, add_element_label = _('Add item'))
30
        form.add_submit('submit', _('Submit'))
31
        form.add_submit('cancel', _('Cancel'))
32
        return form
33

  
34
    def submit_edit(self, form):
35
        for f in ('name', 'xpath', 'number', 'raw_xml',  'regexp_match', 'regexp_replacing', 'select_options'):
36
            widget = form.get_widget(f)
37
            setattr(self.field_prefill, f, widget.parse())
38
        self.field_prefill.store()
39

  
40
class FieldPage(Directory):
41
    _q_exports = ['', 'delete']
42

  
43
    def __init__(self, field_id):
44
        self.field_prefill = FieldPrefill.get(field_id)
45
        self.field_ui = FieldUI(self.field_prefill)
46
        get_response().breadcrumb.append((field_id + '/', field_id))
47

  
48
    def _q_index [html] (self):
49
        form = self.field_ui.form_edit()
50
        redo = False
51

  
52
        if form.get_widget('cancel').parse():
53
            return redirect('..')
54

  
55
        if form.get_widget('select_options') and form.get_widget('select_options').get_widget('add_element').parse():
56
            form.clear_errors()
57
            redo = True
58

  
59
        if redo is False and form.is_submitted() and not form.has_errors():
60
            self.field_ui.submit_edit(form)
61
            return redirect('..')
62

  
63
        get_response().breadcrumb.append( ('edit', _('Edit')) )
64
        html_top('edit', title = _('Edit'))
65
        '<h2>%s</h2>' % _('Edit')
66

  
67
        form.render()
68

  
69
    def delete [html] (self):
70
        form = Form(enctype='multipart/form-data')
71
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
72
                        'You are about to irrevocably delete this field.')))
73
        form.add_submit('submit', _('Submit'))
74
        form.add_submit('cancel', _('Cancel'))
75
        if form.get_widget('cancel').parse():
76
            return redirect('..')
77
        if not form.is_submitted() or form.has_errors():
78
            get_response().breadcrumb.append(('delete', _('Delete')))
79
            html_top('delete_form', title = _('Delete Field'))
80
            '<h2>%s : %s</h2>' % (_('Delete Field'), self.field_prefill.id)
81
            form.render()
82
        else:
83
            self.field_prefill.remove_self()
84
            return redirect('..')
85

  
86

  
87
class FieldsDirectory(Directory):
88
    _q_exports = ['', 'new']
89

  
90
    def __init__(self, form_prefill):
91
        get_response().breadcrumb.append(('fields/', _('Fields')))
92
        self.form_prefill = form_prefill
93

  
94
    def _q_lookup(self, component):
95
        return FieldPage(component)
96

  
97
    def _q_index [html] (self):
98
        html_top('fields', title = _('Fields'))
99
        """<ul id="nav-fields-admin">
100
          <li><a href="new">%s</a></li>
101
        </ul>""" % _('New Field')
102

  
103
        '<ul class="biglist">'
104

  
105
        for field_prefill in FieldPrefill.select(lambda x: x.form_id == self.form_prefill.id):
106
            if not field_prefill.name:
107
                continue
108

  
109
            # Split too long xpath
110
            xpath = field_prefill.xpath
111
            xpath_tokens = xpath.split(str('/'))
112
            if len(xpath_tokens) > 3:
113
                xpath = str('.../') + str('/').join(xpath_tokens[-3:])
114

  
115
            '<li>'
116
            '<strong class="label">%s</strong>' % field_prefill.name
117
            '<br />%s' % xpath
118
            '<p class="commands">'
119
            command_icon('%s/' % field_prefill.id, 'edit')
120
            command_icon('%s/delete' % field_prefill.id, 'remove')
121
            '</p></li>'
122
        '</ul>'
123

  
124
    def new [html] (self):
125
        get_response().breadcrumb.append(('new', _('New')) )
126
        field_prefill = FieldPrefill()
127
        field_prefill.form_id = self.form_prefill.id
128
        field_prefill.store()
129
        return redirect('%s/' % field_prefill.id)
130

  
larpe/admin/forms_prefill.ptl
1
from quixote import get_response, redirect
2
from quixote.directory import Directory
3

  
4
from qommon.form import *
5
from qommon.admin.menu import html_top, command_icon
6

  
7
from form_prefill import FormPrefill
8
from fields_prefill import FieldsDirectory
9

  
10
class FormUI:
11
    def __init__(self, form_prefill):
12
        self.form_prefill = form_prefill
13

  
14
    def form_edit(self):
15
        form = Form(enctype='multipart/form-data')
16
        form.add(StringWidget, 'name', title = _('Form name'), required = True,
17
            size = 50, value = self.form_prefill.name, hint=_('Only used for display'))
18
        form.add(UrlWidget, 'url', title = _('Form address'), required = True,
19
            size = 50, value = self.form_prefill.url)
20
        form.add(StringWidget, 'profile', title = _('ID-WSF data profile'), required = True,
21
            size = 50, value = self.form_prefill.profile, hint=_('Example: urn:liberty:id-sis-pp:2005-05'))
22
        form.add(StringWidget, 'prefix', title = _('ID-WSF data XML prefix'), required = True,
23
            size = 50, value = self.form_prefill.prefix, hint=_('Example: pp'))
24
        form.add_submit('submit', _('Submit'))
25
        form.add_submit('cancel', _('Cancel'))
26
        return form
27

  
28
    def submit_edit(self, form):
29
        for f in ('name', 'url', 'profile', 'prefix'):
30
            widget = form.get_widget(f)
31
            setattr(self.form_prefill, f, widget.parse())
32
        self.form_prefill.store()
33

  
34
class FormPage(Directory):
35
    _q_exports = ['', 'edit', 'delete']
36

  
37
    def __init__(self, form_id):
38
        self.form_prefill = FormPrefill.get(form_id)
39
        self.form_ui = FormUI(self.form_prefill)
40
        get_response().breadcrumb.append((form_id + '/', form_id))
41

  
42
    def _q_index [html] (self):
43
        html_top('forms_prefill', title = 'Form prefilling')
44

  
45
        '<h2>%s</h2>' % _('Form prefilling configuration')
46
        '<dl>'
47
        '<dt><a href="edit">%s</a></dt> <dd>%s</dd>' % (
48
                _('Edit'), _('Configure this form'))
49
        '<dt><a href="fields/">%s</a></dt> <dd>%s</dd>' % (
50
                _('Fields'), _('Configure the fields of this form'))
51
        '</dl>'
52

  
53
    def _q_lookup(self, component):
54
        if component == 'fields':
55
            return FieldsDirectory(self.form_prefill)
56

  
57
    def edit [html] (self):
58
        form = self.form_ui.form_edit()
59
        if form.get_widget('cancel').parse():
60
            return redirect('.')
61

  
62
        if form.is_submitted() and not form.has_errors():
63
            self.form_ui.submit_edit(form)
64
            return redirect('.')
65

  
66
        get_response().breadcrumb.append( ('edit', _('Edit')) )
67
        html_top('edit', title = _('Edit'))
68
        '<h2>%s</h2>' % _('Edit')
69

  
70
        form.render()
71

  
72
    def delete [html] (self):
73
        form = Form(enctype='multipart/form-data')
74
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
75
                        'You are about to irrevocably delete this form.')))
76
        form.add_submit('submit', _('Submit'))
77
        form.add_submit('cancel', _('Cancel'))
78
        if form.get_widget('cancel').parse():
79
            return redirect('..')
80
        if not form.is_submitted() or form.has_errors():
81
            get_response().breadcrumb.append(('delete', _('Delete')))
82
            html_top('delete_form', title = _('Delete Form'))
83
            '<h2>%s : %s</h2>' % (_('Delete Form'), self.form_prefill.id)
84
            form.render()
85
        else:
86
            self.form_prefill.remove_self()
87
            return redirect('..')
88

  
89

  
90
class FormsDirectory(Directory):
91
    _q_exports = ['', 'new']
92

  
93
    def __init__(self, host):
94
        get_response().breadcrumb.append(('forms_prefill/', _('Forms')))
95
        self.host = host
96

  
97
    def _q_lookup(self, component):
98
        return FormPage(component)
99

  
100
    def _q_index [html] (self):
101
        html_top('forms', title = _('Forms'))
102
        """<ul id="nav-forms-admin">
103
          <li><a href="new">%s</a></li>
104
        </ul>""" % _('New Form')
105

  
106
        '<ul class="biglist">'
107

  
108
        for form_prefill in FormPrefill.select(lambda x: x.host_id == self.host.id):
109
            if not form_prefill.name:
110
                continue
111
            '<li>'
112
            '<strong class="label">%s</strong>' % form_prefill.name
113
            url = form_prefill.url
114
            '<br /><a href="%s">%s</a>' % (url, url)
115
            '<p class="commands">'
116
            command_icon('%s/' % form_prefill.id, 'edit')
117
            command_icon('%s/delete' % form_prefill.id, 'remove')
118
            '</p></li>'
119
        '</ul>'
120

  
121
    def new [html] (self):
122
        get_response().breadcrumb.append(('new', _('New')) )
123
        form_prefill = FormPrefill()
124
        form_prefill.host_id = self.host.id
125
        form_prefill.store()
126
        return redirect('%s/edit' % form_prefill.id)
127

  
larpe/admin/hosts.ptl
1
import os
2
import urllib
3
import urlparse
4

  
5
from quixote import redirect, get_request, get_response, get_publisher
6
from quixote.directory import Directory
7

  
8
import lasso
9

  
10
from qommon.admin.menu import html_top, command_icon
11
from qommon import get_cfg
12
from qommon.form import *
13
from qommon.misc import http_get_page, get_abs_path
14

  
15
from larpe import site_authentication
16
from larpe import errors
17
from larpe import misc
18
from larpe.hosts import Host
19
from larpe.admin.apache import Location
20
from larpe.admin.liberty_utils import *
21
from larpe.admin.apache import write_apache2_vhosts
22
from larpe.admin.forms_prefill import FormsDirectory
23
from larpe.Defaults import DATA_DIR
24
from larpe.plugins import site_authentication_plugins
25

  
26
def check_basic_configuration(form):
27
    get_publisher().reload_cfg()
28
    # Check reversed_hostname and reversed_directory
29
    reversed_hostname = form.get_widget('reversed_hostname').parse()
30
    reversed_directory = form.get_widget('reversed_directory').parse()
31
    if reversed_hostname == get_publisher().cfg['proxy_hostname'] and not reversed_directory:
32
        form.set_error('reversed_hostname',
33
            _('You must either choose a different hostname from Larpe or specify a reversed directory'))
34

  
35
def convert_label_to_name(label):
36
    '''Build host name from host label'''
37
    name = label.lower()
38
    invalid_characters = [' ', "'"]
39
    for char in invalid_characters:
40
        name = name.replace(char, '_')
41
    return name
42

  
43
class DictWidget(Widget):
44
    def render_content [html] (self):
45
        self.render_br = False
46
        if self.value['enabled'] is True:
47
            htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled', checked='checked')
48
        else:
49
            htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled')
50
        ' ' + self.name + ' &nbsp;'
51
        htmltag('input', xml_end=True, type='text', name=self.name, value=self.value['value'], size='35', **self.attrs)
52

  
53
    def _parse(self, request):
54
        enabled = request.form.get(self.name + '_enabled')
55
        value = request.form.get(self.name)
56
        self.value = { 'enabled': enabled, 'value': value }
57

  
58

  
59
class ConfigurationAssistant(Directory):
60
    _q_exports = ['start', 'check_new_address', 'modify_site_address_and_name',
61
                  'authentication_and_logout_adresses', 'check_auto_detected_configuration',
62
                  'credentials', 'send_authentication_request', 'check_authentication',
63
                  'see_authentication_response', 'see_response_html_page',
64
                  'see_bad_response_html_page', 'authentication_success_criteria',
65
                  'modify_authentication_request', 'auth_request_post_parameters',
66
                  'auth_request_http_headers', 'sso_init_link', 'metadatas',
67
                  'check_full_configuration', 'advanced_options']
68

  
69
    def __init__(self, host):
70
        self.host = host
71

  
72
    def html_top [html] (self, title):
73
        html_top('hosts', title)
74
        '<h2>%s</h2>' % title
75

  
76
    def start [html] (self):
77
        # Check the global domain name has been previously set
78
        get_publisher().reload_cfg()
79
        if not get_cfg('domain_names') or not get_cfg('domain_names')[0]:
80
            get_response().breadcrumb.append(('start', _('Basic configuration')))
81
            self.html_top(_('Need domain name configuration'))
82
            return htmltext(_('''Before configuring hosts, you must
83
<a href="../../../settings/domain_names">setup a global domain name</a> in
84
%(settings)s menu.''') % { 'settings': _('Settings') })
85

  
86
        form = self.form_start()
87

  
88
        if form.get_widget('cancel').parse():
89
            return redirect('../..')
90

  
91
        connection_failure = None
92
        if form.is_submitted() and not form.has_errors():
93
            try:
94
                self.submit_start_form(form)
95
            except Exception, e:
96
                connection_failure = e
97
            else:
98
                if form.get_widget('terminate').parse():
99
                    return redirect('..')
100
                return redirect('check_new_address')
101

  
102
        get_response().breadcrumb.append(('start', _('Basic configuration')))
103
        self.html_top(_('Step 1 - Basic configuration'))
104

  
105
        if connection_failure:
106
            '<div class="errornotice">%s</div>' % connection_failure
107

  
108
        form.render()
109

  
110
    def form_start(self):
111
        form = Form(enctype='multipart/form-data')
112
        form.add(UrlWidget, 'orig_site', title = _('Original site root address'), required = True,
113
                size = 50, value = self.host.orig_site,
114
                hint = _('If your site address is http://test.org/index.php, put http://test.org/ here'))
115
        get_publisher().reload_cfg()
116
        if get_cfg('proxy', {}).get('enabled'):
117
            form.add(CheckboxWidget, 'use_proxy', title = _('Use a proxy'),
118
                    hint = _("Uncheck it if Larpe doesn't need to use the proxy to connect to this site"),
119
                    value = self.host.use_proxy)
120
        else:
121
            form.add(HtmlWidget, htmltext('<p>%s</p>' % \
122
                _('''If Larpe needs to use a proxy to connect to this site, you must first configure
123
 it in <a href="../../../settings/proxy">global proxy parameters</a>.''')))
124
        form.add_submit('cancel', _('Cancel'))
125
        form.add_submit('submit', _('Next'))
126
        form.add_submit('terminate', _('Terminate'))
127
        return form
128

  
129
    def submit_start_form(self, form):
130
        fields = ['orig_site']
131
        use_proxy = get_cfg('proxy', {}).get('enabled')
132
        if use_proxy:
133
            fields += ['use_proxy']
134
        for f in fields:
135
            setattr(self.host, f, form.get_widget(f).parse())
136

  
137
        # If no proxy is setup yet, set use_proxy to False for new hosts in case a proxy is later configured
138
        if not use_proxy:
139
            self.host.use_proxy = False
140

  
141
        # Remove what is after the last '/'
142
        #self.host.orig_site = '/'.join(self.host.orig_site.split('/')[:-1])
143

  
144
        html_page = self.get_data_after_redirects(self.host.orig_site)
145

  
146
        if not self.host.label:
147
            # Look for html title in original site index page
148
            regexp = re.compile("""<title.*?>(.*?)</title>""", re.DOTALL | re.IGNORECASE)
149
            title = regexp.findall(html_page)
150
            if title:
151
                self.host.label = title[0]
152
            else:
153
                self.host.label = 'Untitled'
154
            # If another site already uses this site title, add trailings "_" until we find an available name
155
            existing_label = True
156
            while existing_label is True:
157
                for any_host in Host.select():
158
                    if any_host.id != self.host.id and self.host.label == any_host.label:
159
                        self.host.label += '_'
160
                        break
161
                else:
162
                    existing_label = False
163

  
164
            # Fill host.name attribute
165
            self.host.name = convert_label_to_name(self.host.label)
166

  
167
        if not self.host.scheme:
168
            # Get tokens from orig site url
169
            orig_scheme, rest = urllib.splittype(self.host.orig_site)
170
            orig_host, rest = urllib.splithost(rest)
171

  
172
            get_publisher().reload_cfg()
173
            # Set url scheme (HTTP or HTTPS)
174
            # TODO: Handle the option "Both"
175
            if get_cfg('sites_url_scheme'):
176
                self.host.scheme = get_cfg('sites_url_scheme')
177
            else:
178
                self.host.scheme = orig_scheme
179

  
180
        if not self.host.reversed_hostname:
181
            # Build a new domain name
182
            short_name = orig_host.split('.')[0]
183
            if short_name == 'www':
184
                short_name = orig_host.split('.')[1]
185
            self.host.reversed_hostname = '%s.%s' % (short_name, get_cfg('domain_names')[0])
186
            # If another site already uses this domain name, add some trailing "_" until we find an available name
187
            existing_domain = True
188
            while existing_domain is True:
189
                for any_host in Host.select():
190
                    if any_host.id != self.host.id and self.host.reversed_hostname == any_host.reversed_hostname:
191
                        self.host.reversed_hostname += '-'
192
                        break
193
                else:
194
                    existing_domain = False
195
            self.host.reversed_directory = None
196

  
197
        if not self.host.new_url:
198
            # New url for this host
199
            self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
200
            # FIXME: Check if the new domain name already exists
201

  
202
            # New url for this host
203
            #        self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
204
            #        if self.host.reversed_directory is not None:
205
            #            self.host.new_url += '%s/' % self.host.reversed_directory
206

  
207
        self.host.store()
208
        write_apache2_vhosts()
209

  
210
    # XXX: Should use the FancyURLopener class instead when it supports proxies
211
    def get_data_after_redirects(self, start_url):
212
        if not start_url:
213
            return ''
214
        status = 302
215
        location = None
216
        while status // 100 == 3:
217
            if location is None:
218
                url = start_url
219
            elif location.startswith('http'):
220
                # Location is an absolute path
221
                url = location
222
            else:
223
                # Location is a relative path
224
                url = urlparse.urljoin(start_url, location)
225
            response, status, data, auth_headers = http_get_page(url, use_proxy=self.host.use_proxy)
226
            location = response.getheader('Location', None)
227
        return data
228

  
229
    def create_dirs(self):
230
        # Hack : sites must use the configuration which is stored in main Larpe directory,
231
        # but they need to have a directory named with their hostname, which will contain the
232
        # main domain name for Larpe so they know where is the main configuration
233
        hostname_dir = get_abs_path(os.path.join('..', self.host.reversed_hostname))
234
        if not os.path.exists(hostname_dir):
235
            os.mkdir(hostname_dir)
236
        # Load the configuration from the main directory
237
        get_publisher().reload_cfg()
238
        # Write it in the site directory
239
        get_publisher().write_cfg(hostname_dir)
240

  
241
        # Storage directories
242
        if not self.host.reversed_directory:
243
            reversed_dir = 'default'
244
        else:
245
            reversed_dir = self.host.reversed_directory
246
        self.host.site_dir = \
247
            os.path.join(get_publisher().app_dir, 'sp', self.host.reversed_hostname, reversed_dir)
248
        user_dir = os.path.join(self.host.site_dir, 'users')
249
        token_dir = os.path.join(self.host.site_dir, 'tokens')
250
        filter_dir = os.path.join(self.host.site_dir, 'filters')
251
        for dir in (self.host.site_dir, user_dir, token_dir, filter_dir):
252
            if not os.path.isdir(dir):
253
                os.makedirs(dir)
254

  
255
    def generate_ssl_keys(self):
256
        # Generate SSL keys
257
        private_key_path = os.path.join(self.host.site_dir, 'private_key.pem')
258
        public_key_path = os.path.join(self.host.site_dir, 'public_key.pem')
259
        if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
260
            set_provider_keys(private_key_path, public_key_path)
261
        self.host.private_key = private_key_path
262
        self.host.public_key = public_key_path
263

  
264
    def generate_metadatas(self):
265
        metadata_cfg = {}
266

  
267
        # Organization name
268
        self.host.organization_name = self.host.label
269
        metadata_cfg['organization_name'] = self.host.organization_name
270

  
271
        # Base URL
272
        base_url = '%s://%s%s/liberty/%s/liberty' % (self.host.scheme,
273
                                                     self.host.reversed_hostname,
274
                                                     get_request().environ['SCRIPT_NAME'],
275
                                                     self.host.name)
276
        metadata_cfg['base_url'] = base_url
277
        self.host.base_url = base_url
278

  
279
        if lasso.SAML2_SUPPORT:
280
            saml2_base_url = '%s://%s%s/liberty/%s/saml' % (self.host.scheme,
281
                                                         self.host.reversed_hostname,
282
                                                         get_request().environ['SCRIPT_NAME'],
283
                                                         self.host.name)
284
            metadata_cfg['saml2_base_url'] = saml2_base_url
285
            self.host.saml2_base_url = saml2_base_url
286

  
287
        # Provider Id
288
        provider_id = '%s/metadata' % base_url
289
        metadata_cfg['provider_id'] = provider_id
290
        self.host.provider_id = provider_id
291

  
292
        if lasso.SAML2_SUPPORT:
293
            saml2_provider_id = '%s/metadata' % saml2_base_url
294
            metadata_cfg['saml2_provider_id'] = saml2_provider_id
295
            self.host.saml2_provider_id = saml2_provider_id
296

  
297
        # Read public key
298
        public_key = ''
299
        if self.host.public_key is not None and os.path.exists(self.host.public_key):
300
            metadata_cfg['signing_public_key'] = open(self.host.public_key).read()
301

  
302
        # Write metadatas
303
        metadata_path = os.path.join(self.host.site_dir, 'metadata.xml')
304
        open(metadata_path, 'w').write(get_metadata(metadata_cfg))
305
        self.host.metadata = metadata_path
306

  
307
        if lasso.SAML2_SUPPORT:
308
            saml2_metadata_path = os.path.join(self.host.site_dir, 'saml2_metadata.xml')
309
            open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
310
            self.host.saml2_metadata = saml2_metadata_path
311

  
312
    def check_new_address [html] (self):
313
        form = Form(enctype='multipart/form-data')
314
        form.add_submit('cancel', _('Previous'))
315
        form.add_submit('submit', _('Next'))
316
        form.add_submit('terminate', _('Terminate'))
317

  
318
        if form.get_widget('cancel').parse():
319
            return redirect('start')
320

  
321
        if form.is_submitted():
322
            self.create_dirs()
323
            if self.host.private_key is None:
324
                self.generate_ssl_keys()
325
            self.generate_metadatas()
326
            self.host.store()
327

  
328
            if form.get_widget('terminate').parse():
329
                return redirect('..')
330
            return redirect('authentication_and_logout_adresses')
331

  
332
        get_response().breadcrumb.append(('check_new_address', _('Check site address and name')))
333
        self.html_top(_('Step 2 - Check the new site address works'))
334

  
335
        '<h3>%s</h3>' % _('DNS configuration')
336

  
337
        '<p>%s</p>' % _('''Before opening the following link, ensure you have configured your DNS
338
for this address. If you don't have a DNS server and you just want to test Larpe, add this
339
domain name in the file "/etc/hosts".''')
340

  
341
        '<p>%s</p>' % _('''Then you can open this link in a new window or tab and see if your site
342
is displayed. If it's ok, you can click the "%(next)s" button. Otherwise, click the "%(previous)s"
343
button and check your settings.''') % {'next': _('Next'), 'previous': _('Previous')}
344

  
345
        '<h3>%s</h3>' % _('Site adress and name')
346

  
347
        '<p>%s' % _('The new address of this site is ')
348
        '<a href="%s">%s</a><br/>' % (self.host.new_url, self.host.new_url)
349
        '%s</p>' % _('The name of this site is "%s".') % self.host.label
350
        '<p>%s</p>' % htmltext(_('''You can also <a href="modify_site_address_and_name">
351
modify the address or the name of this site</a>'''))
352

  
353
        form.render()
354

  
355
    def modify_site_address_and_name [html] (self):
356
        form = self.form_modify_site_address_and_name()
357

  
358
        if form.get_widget('cancel').parse():
359
            return redirect('check_new_address')
360

  
361
        if form.is_submitted() and not form.has_errors():
362
            label = form.get_widget('label').parse()
363
            name = convert_label_to_name(label)
364
            for any_host in Host.select():
365
                if any_host.id != self.host.id and name == any_host.name:
366
                    form.set_error('label', _('An host with the same name already exists'))
367
                    break
368

  
369
        if form.is_submitted() and not form.has_errors():
370
            self.submit_modify_site_address_and_name_form(form)
371
            return redirect('check_new_address')
372

  
373
        get_response().breadcrumb.append(('modify_site_address_and_name', _('Modify site address and name')))
374
        self.html_top(_('Modify site address and name'))
375

  
376
        form.render()
377

  
378
    def form_modify_site_address_and_name(self):
379
        form = Form(enctype='multipart/form-data')
380
        form.add(UrlWidget, 'new_url', title = _('Address'), required = True,
381
                size = 50, value = self.host.new_url)
382
        form.add(StringWidget, 'label', title = _('Name'), required = True,
383
                size = 50, value = self.host.label)
384
        form.add_submit('submit', _('Submit'))
385
        form.add_submit('cancel', _('Cancel'))
386
        return form
387

  
388
    def submit_modify_site_address_and_name_form(self, form):
389
        fields = ['new_url', 'label']
390
        for f in fields:
391
            setattr(self.host, f, form.get_widget(f).parse())
392

  
393
        # Split url to retrieve components
394
        tokens = urlparse.urlparse(self.host.new_url)
395
        self.host.scheme = tokens[0]
396
        self.host.reversed_hostname = tokens[1]
397
        self.host.reversed_directory = tokens[2]
398
        if self.host.reversed_directory.startswith('/'):
399
            self.host.reversed_directory = self.host.reversed_directory[1:]
400

  
401
        # Fill host.name attribute
402
        self.host.name = convert_label_to_name(self.host.label)
403

  
404
        self.host.store()
405
        write_apache2_vhosts()
406

  
407
    def authentication_and_logout_adresses [html] (self):
408
        form = self.form_authentication_and_logout_adresses()
409

  
410
        if form.get_widget('cancel').parse():
411
            return redirect('check_new_address')
412

  
413
        if form.is_submitted() and not form.has_errors():
414
            self.submit_authentication_and_logout_adresses_form(form)
415
            if form.get_widget('terminate').parse():
416
                return redirect('..')
417
            return redirect('check_auto_detected_configuration')
418

  
419
        get_response().breadcrumb.append(('authentication_and_logout_adresses', _('Authentication and logout')))
420
        self.html_top(_('Step 3 - Configure authentication and logout pages'))
421

  
422
        form.render()
423

  
424
    def form_authentication_and_logout_adresses(self):
425
        form = Form(enctype='multipart/form-data')
426
        form.add(ValidUrlWidget, 'auth_url', title = _('Authentication form page address'),
427
                hint = _('Address of a page on the site which contains the authentication form'),
428
                required = True, size = 50, value = self.host.auth_url)
429
        form.add(ValidUrlWidget, 'logout_url', title = _('Logout address'), required = False,
430
                hint = _('Address of the logout link on the site'),
431
                size = 50, value = self.host.logout_url)
432
        form.add_submit('cancel', _('Previous'))
433
        form.add_submit('submit', _('Next'))
434
        form.add_submit('terminate', _('Terminate'))
435
        return form
436

  
437
    def submit_authentication_and_logout_adresses_form(self, form):
438
        fields = ['auth_url', 'logout_url']
439
        for f in fields:
440
            setattr(self.host, f, form.get_widget(f).parse())
441
        self.host.auth_form_url = self.host.auth_url
442

  
443
        if not self.host.http_headers:
444
            self.host.http_headers = {
445
                'Content-Type': { 'enabled': True, 'value': 'application/x-www-form-urlencoded', 'immutable': False },
446
                'X-Forwarded-For': { 'enabled': True, 'value': _('(computed automatically)'), 'immutable': True },
447
                'X-Forwarded-Host': { 'enabled': True, 'value': self.host.reversed_hostname, 'immutable': False },
448
            }
449

  
450
        self.auto_detect_configuration()
451
        self.host.store()
452
        write_apache2_vhosts()
453

  
454
    def check_auto_detected_configuration [html] (self):
455
        plugins_name = site_authentication_plugins.get_plugins_name()
456
        plugins_name.append(None)
457
        plugins_name.sort()
458
        form = Form(enctype='multipart/form-data')
459
        form.add(SingleSelectWidget, 'plugin', title = _('Plugin'), required = False,
460
                hint = _('You can force a plugin'),
461
                value = self.host.site_authentication_plugin,
462
                options = plugins_name)
463
        form.add_submit('cancel', _('Previous'))
464
        form.add_submit('submit', _('Next'))
465
        form.add_submit('terminate', _('Terminate'))
466

  
467
        if form.get_widget('cancel').parse():
468
            return redirect('authentication_and_logout_adresses')
469

  
470
        if form.is_submitted():
471
            self.host.site_authentication_plugin = form.get_widget('plugin').parse()
472
            self.host.store()
473
            if form.get_widget('terminate').parse():
474
                write_apache2_vhosts()
475
                return redirect('..')
476
            return redirect('credentials')
477

  
478
        get_response().breadcrumb.append(('check_auto_detected_configuration', _('Auto detected configuration')))
479
        self.html_top(_('Step 4 - Check automatically detected configuration for the authentication form'))
480

  
481
        host_attrs = (
482
            ('auth_check_url', _('Address where the authentication form must be sent')),
483
            ('login_field_name', _('Name of the login field')),
484
            ('password_field_name', _('Name of the password field')),
485
        )
486

  
487
        html_fields = ''
488
        success = True
489
        for attr, name in host_attrs:
490
            color = 'black'
491
            if attr in ('auth_check_url', 'login_field_name', 'password_field_name') and \
492
                    not getattr(self.host, str(attr)):
493
                color = 'red'
494
                success = False
495
            html_fields += '<div style="margin-bottom: 0.1em; font-weight: bold; color: %s;">%s</div>' % (color, name)
496
            html_fields += '<div style="margin-left: 1em; margin-bottom: 0.3em; color: %s;">%s</div>' % \
497
                (color, getattr(self.host, str(attr)))
498
            if getattr(self.host, str(attr)) == '':
499
                html_fields += '<br />'
500

  
501
        '<p>'
502
        if success:
503
            htmltext(_('''\
504
The following authentication form parameters have been detected. If they look right, you can go to the next step.
505
If you think they are wrong, go back and check your settings then try again.
506
'''))
507
        else:
508
            htmltext(_('''\
509
The following authentication form parameters in red haven't been correctly detected. Go back and check
510
your settings then try again.
511
'''))
512
        '</p>'
513

  
514
        html_fields
515
        form.render()
516

  
517
    def credentials [html] (self):
518
        form = self.form_credentials()
519

  
520
        if form.get_widget('cancel').parse():
521
            return redirect('check_auto_detected_configuration')
522

  
523
        if form.is_submitted() and not form.has_errors():
524
            self.submit_credentials_form(form)
525
            if form.get_widget('terminate').parse():
526
                return redirect('..')
527
            return redirect('send_authentication_request')
528

  
529
        get_response().breadcrumb.append(('credentials', _('Credentials')))
530
        self.html_top(_('Step 5 - Fill in a valid username/password for this site'))
531
        form.render()
532

  
533
    def form_credentials(self):
534
        form = Form(enctype='multipart/form-data')
535
        form.add(StringWidget, 'username', title = _('Username'), required = True,
536
                size = 30, value = self.host.valid_username)
537
        form.add(PasswordWidget, 'password', title = _('Password'), required = True,
538
                size = 30, value = self.host.valid_password)
539
        for name, values in self.host.select_fields.iteritems():
540
            options = []
541
            if values:
542
                for value in values:
543
                    options.append(value)
544
                form.add(SingleSelectWidget, name, title = name.capitalize(),
545
                    value = values[0], options = options)
546
        form.add_submit('cancel', _('Previous'))
547
        form.add_submit('submit', _('Next'))
548
        form.add_submit('terminate', _('Terminate'))
549
        return form
550

  
551
    def submit_credentials_form(self, form):
552
        self.host.valid_username = form.get_widget('username').parse()
553
        self.host.valid_password = form.get_widget('password').parse()
554
        self.host.valid_select = {}
555
        for name, values in self.host.select_fields.iteritems():
556
            if form.get_widget(name):
557
                self.host.valid_select[name] = form.get_widget(name).parse()
558

  
559
        self.host.store()
560

  
561
    def send_authentication_request(self):
562
        site_auth = site_authentication.get_site_authentication(self.host)
563

  
564
        # Request with good credentials
565
        self.host.auth_request_status, self.host.auth_request_data = site_auth.local_auth_check_dispatch(
566
            self.host.valid_username, self.host.valid_password, self.host.valid_select)
567
        self.host.auth_request_success, self.host.auth_request_return_content = \
568
            site_auth.check_auth(self.host.auth_request_status, self.host.auth_request_data)
569

  
570
        # Request with bad credentials
571
        self.host.auth_bad_request_status, self.host.auth_bad_request_data = site_auth.local_auth_check_dispatch(
572
            'this_is_a_bad_login', 'this_is_a_bad_password', {})
573
        self.host.auth_bad_request_success, self.host.auth_bad_request_return_content = \
574
            site_auth.check_auth(self.host.auth_bad_request_status, self.host.auth_bad_request_data)
575

  
576
        self.host.store()
577

  
578
        return redirect('check_authentication')
579

  
580
    def check_authentication [html] (self):
581
        form = Form(enctype='multipart/form-data')
582
        form.add_submit('cancel', _('Previous'))
583
        form.add_submit('submit', _('Next'))
584
        form.add_submit('terminate', _('Terminate'))
585

  
586
        if form.get_widget('cancel').parse():
587
            return redirect('credentials')
588

  
589
        if form.is_submitted():
590
            if form.get_widget('terminate').parse():
591
                return redirect('..')
592
            return redirect('sso_init_link')
593

  
594
        get_response().breadcrumb.append(('check_authentication', _('Check authentication')))
595
        self.html_top(_('Step 6 - Check the authentication process'))
596

  
597
        if self.host.auth_request_success:
598
            good_cred_status = 'Success <span style="color: green">[OK]</span>'
599
        else:
600
            good_cred_status = 'Failed <span style="color: red">[KO]</span'
601

  
602
        if not self.host.auth_bad_request_success:
603
            bad_cred_status = 'Failed <span style="color: green">[OK]</span>'
604
        else:
605
            bad_cred_status = 'Success <span style="color: red">[KO]</span>'
606

  
607
        '<p>Results of authentication requests :</p>\n'
608
        '<ul>\n'
609
        '\t<li>With good credentials : %s</li>' % good_cred_status
610
        '\t<li>With bad credentials : %s</li>' % bad_cred_status
611
        '</ul>\n'
612

  
613
        if self.host.auth_request_success and not self.host.auth_bad_request_success :
614
            '<p>%s</p>\n' % _('Authentication succeeded ! You can go to the next step.')
615
        else:
616
            '<p>%s</p>\n' % _('Authentication has failed. To resolve this problem, you can :')
617
            '<ul>\n'
618
            '\t<li><a href="send_authentication_request">%s</a></li>\n' % \
619
                _('Try authentication again')
620
            '\t<li><a href="see_authentication_response">%s</a></li>\n' % \
621
                _('See the response of the authentication requests')
622
            '\t<li><a href="modify_authentication_request">%s</a></li>\n' % \
623
                _('Modify the parameters of the authentication requests')
624
            '\t<li><a href="authentication_success_criteria">%s</a></li>\n' % \
625
                _('Change the way Larpe detects the authentication is successful or not')
626
            '\t<li>%s</li>\n' % _('Go back and change your username and/or password')
627
            '</ul>\n'
628

  
629
        form.render()
630

  
631
    def see_authentication_response [html] (self):
632
        get_response().breadcrumb.append(('see_authentication_response', _('Authentication response')))
633
        self.html_top(_('Authentication response'))
634

  
635
        '<h3>%s</h3>' % _('Response of the request with good credentials')
636

  
637
        '<div style="margin-bottom: 0.1em; font-weight: bold; color: black;">%s</div>' % \
638
            str(_('HTTP status code'))
639
        '<div style="margin-left: 1em; margin-bottom: 0.3em; color: black;">%s (%s)</div>' % \
640
            (self.host.auth_request_status, status_reasons[self.host.auth_request_status])
641

  
642
        '<dl>'
643
        '<dt><a href="see_bad_response_html_page">%s</a></dt>' % _('See HTML page')
644
        '</dl>'
645

  
646
        '<h3>%s</h3>' % _('Response of the request with bad credentials')
647

  
648
        '<div style="margin-bottom: 0.1em; font-weight: bold; color: black;">%s</div>' % \
649
            str(_('HTTP status code'))
650
        '<div style="margin-left: 1em; margin-bottom: 0.3em; color: black;">%s (%s)</div>' % \
651
            (self.host.auth_bad_request_status, status_reasons[self.host.auth_bad_request_status])
652

  
653
        '<dl>'
654
        '<dt><a href="see_response_html_page">%s</a></dt>' % _('See HTML page')
655
        '</dl>'
656

  
657
        '<div class="buttons"><a href="check_authentication"><input type="button" value="%s" /></a></div><br />' % _('Back')
658

  
659
    def see_response_html_page (self):
660
        return self.host.auth_request_data
661

  
662
    def see_bad_response_html_page (self):
663
        return self.host.auth_bad_request_data
664

  
665
    def authentication_success_criteria [html] (self):
666
        form = self.form_authentication_success_criteria()
667

  
668
        if form.get_widget('cancel').parse():
669
            return redirect('check_authentication')
670

  
671
        if form.is_submitted() and not form.has_errors():
672
            self.submit_authentication_success_criteria_form(form)
673
            return redirect('check_authentication')
674

  
675
        get_response().breadcrumb.append(('authentication_success_criteria', _('Authentication success criteria')))
676
        self.html_top(_('Authentication success criteria'))
677

  
678
        form.render()
679

  
680
    def form_authentication_success_criteria(self):
681
        form = Form(enctype='multipart/form-data')
682
        form.add(RadiobuttonsWidget, 'auth_system', title = _('Authentication system of the original site'),
683
                options=[
684
                    ('password', _('Check the existence of a password field'), 'password'),
685
                    ('match_text', _('Match some text to detect an authentication failure'), 'match_text'),
686
                ],
687
                sort=False,
688
                delim=htmltext('<br />'),
689
                value = self.host.auth_system)
690
        form.add(RegexStringWidget, 'auth_match_text', title = _('Text to match in case of authentication failure'),
691
                required = False, size = 50, value = self.host.auth_match_text)
692
        form.add_submit('submit', _('Submit'))
693
        form.add_submit('cancel', _('Cancel'))
694
        return form
695

  
696
    def submit_authentication_success_criteria_form(self, form):
697
        for f in ('auth_system', 'auth_match_text'):
698
            value = form.get_widget(f).parse()
699
            setattr(self.host, f, value)
700

  
701
        self.host.store()
702

  
703
    def modify_authentication_request [html] (self):
704
        get_response().breadcrumb.append(('modify_authentication_request', _('Authentication request')))
705
        self.html_top(_('Modify the parameters of the authentication requests'))
706

  
707
        '<dl>'
708
        '<dt><a href="auth_request_post_parameters">%s</a></dt> <dd>%s</dd>' % (
709
                _('Modify POST parameters'), _('Configure the form attributes that will be sent within the authentication POST requests'))
710
        '<dt><a href="auth_request_http_headers">%s</a></dt> <dd>%s</dd>' % (
711
                _('Modify HTTP headers'), _('Configure the HTTP headers of the authentication requests made by Larpe'))
712
        '</dl>'
713
        '<div class="buttons"><a href="check_authentication"><input type="button" value="%s" /></a></div><br />' % _('Back')
714

  
715
    def auth_request_post_parameters [html] (self):
716
        form = self.form_auth_request_post_parameters()
717

  
718
        if form.get_widget('cancel').parse():
719
            return redirect('modify_authentication_request')
720

  
721
        if form.is_submitted() and not form.has_errors():
722
            self.submit_auth_request_post_parameters_form(form)
723
            return redirect('modify_authentication_request')
724

  
725
        get_response().breadcrumb.append(('auth_request_post_parameters', _('POST parameters')))
726
        self.html_top(_('Configure POST parameters'))
727

  
728
        '<p>%s</p>' % _('''Here are the detected form fields that will be sent as parameters of the
729
authentication POST request. You can desactivate some or all of them, or change their value.''')
730

  
731
        form.render()
732

  
733
    def form_auth_request_post_parameters(self):
734
        form = Form(enctype='multipart/form-data')
735
        for name, value in self.host.post_parameters.iteritems():
736
            if value['immutable']:
737
                form.add(DictWidget, name, value, disabled = 'disabled')
738
            else:
739
                form.add(DictWidget, name, value)
740
        form.add_submit('submit', _('Submit'))
741
        form.add_submit('cancel', _('Cancel'))
742
        return form
743

  
744
    def submit_auth_request_post_parameters_form(self, form):
745
        for name, old_value in self.host.post_parameters.iteritems():
746
            value = form.get_widget(name).parse()
747
            if value['enabled'] == 'on':
748
                old_value['enabled'] = True
749
            else:
750
                old_value['enabled'] = False
751
            if old_value['immutable'] is False:
752
                old_value['value'] = value['value']
753
            self.host.post_parameters[name] = old_value
754
        self.host.store()
755

  
756
    def auth_request_http_headers [html] (self):
757
        form = self.form_auth_request_http_headers()
758

  
759
        if form.get_widget('cancel').parse():
760
            return redirect('modify_authentication_request')
761

  
762
        if form.is_submitted() and not form.has_errors():
763
            self.submit_auth_request_http_headers_form(form)
764
            return redirect('modify_authentication_request')
765

  
766
        get_response().breadcrumb.append(('auth_request_http_headers', _('HTTP headers')))
767
        self.html_top(_('Configure HTTP headers'))
768

  
769
        '<p>%s</p>' % _('''Here are the HTTP headers that will be sent within the authentication
770
POST request. You can desactivate some or all of them, or change their value.''')
771

  
772
        form.render()
773

  
774
    def form_auth_request_http_headers(self):
775
        form = Form(enctype='multipart/form-data')
776
        for name, value in self.host.http_headers.iteritems():
777
            if value['immutable']:
778
                form.add(DictWidget, name, value, disabled = 'disabled')
779
            else:
780
                form.add(DictWidget, name, value)
781
        form.add(HtmlWidget, htmltext('<p>%s</p>' % \
782
            _('The headers "Host", "Accept-Encoding" and "Content-Length" will also automatically be sent.')))
783
        if get_cfg('proxy', {}).get('enabled') and self.host.use_proxy:
784
            form.add(HtmlWidget, htmltext('<p>%s</p>' % \
785
                _('''As Larpe uses a proxy for this site, the headers "Proxy-Authorization",
786
"Proxy-Connection" and "Keep-Alive" will be sent as well.''')))
787
        form.add_submit('submit', _('Submit'))
788
        form.add_submit('cancel', _('Cancel'))
789
        return form
790

  
791
    def submit_auth_request_http_headers_form(self, form):
792
        for name, old_value in self.host.http_headers.iteritems():
793
            value = form.get_widget(name).parse()
794
            if value['enabled'] == 'on':
795
                old_value['enabled'] = True
796
            else:
797
                old_value['enabled'] = False
798
            if old_value['immutable'] is False:
799
                old_value['value'] = value['value']
800
            self.host.http_headers[name] = old_value
801
        self.host.store()
802

  
803
    def generate_apache_filters(self):
804
        self.host.apache_python_paths = []
805
        self.host.apache_output_python_filters = []
806
        site_auth = site_authentication.get_site_authentication(self.host)
807
        output_filters = site_auth.output_filters
808
        replace_login_form = self.host.auth_form_places == 'form_everywhere' and \
809
                self.host.auth_form_action
810
        if replace_login_form and not 'output_replace_form' in output_filters:
811
            output_filters.append('output_replace_form')
812
        if output_filters:
813
            location = Location(self.host)
814
            conf = { 'auth_form_action': self.host.auth_form_action,
815
                    'name': self.host.name,
816
                    'larpe_dir': get_publisher().app_dir,
817
                    'logout_url': location.new_logout_url,
818
                    'login_url': location.new_auth_url }
819
            # Set Python filter path for Apache configuration
820
            python_path = os.path.join(self.host.site_dir, 'filters')
821
            if python_path not in self.host.apache_python_paths:
822
                self.host.apache_python_paths.append(python_path)
823

  
824
            for filter in output_filters:
825
                python_file = open(
826
                        os.path.join(self.host.site_dir, 'filters', filter + ".py"),
827
                        'w')
828
                python_file.write(
829
                        open(os.path.join(DATA_DIR, "filters", filter + ".py")).read() % conf
830
                        )
831
                if not filter in self.host.apache_output_python_filters:
832
                    self.host.apache_output_python_filters.append(filter)
833

  
834
    def sso_init_link [html] (self):
835
        form = self.form_sso_init_link()
836

  
837
        if form.get_widget('cancel').parse():
838
            return redirect('check_authentication')
839

  
840
        if form.is_submitted() and not form.has_errors():
841
            self.submit_sso_init_link_form(form)
842
            if form.get_widget('terminate').parse():
843
                return redirect('..')
844
            return redirect('metadatas')
845

  
846
        get_response().breadcrumb.append(('sso_init_link', _('SSO initiation')))
847
        self.html_top(_('Step 7 - Configure how a Single Sign On can be initiated'))
848

  
849
        '<p>%s\n' % _('Most sites use one of the following 2 ways to allow users to initialise an authentication :')
850
        '\t<ol>\n'
851
        '\t\t<li>%s</li>\n' % \
852
            _('''The site has a single authentication page. It redirects users to this page when
853
they click a "Login" button or try to access a page which require users to be authenticated.''')
854
        '\t\t<li>%s</li>\n' % \
855
            _('''The site includes an authentication form in most or all of his pages. Users can
856
authenticate on any of these pages, and don't need to be redirected to a separate authentication page.''')
857
        '\t</ol>\n'
858
        '</p>\n'
859

  
860
        '<p>%s</p>' % _('Select the way your site works :')
861

  
862
        form.render()
863

  
864
    def form_sso_init_link(self):
865
        form = Form(enctype='multipart/form-data')
866
        form.add(RadiobuttonsWidget, 'auth_form_places',
867
                options=[
868
                    ('form_once', _('The site has a single authentication page'), 'form_once'),
869
                    ('form_everywhere', _('The site includes an authentication form in most or all pages'), 'form_everywhere'),
870
                ],
871
                sort=False, required = True, delim=htmltext('<br />'), value = self.host.auth_form_places)
872
        form.add_submit('cancel', _('Previous'))
873
        form.add_submit('submit', _('Next'))
874
        form.add_submit('terminate', _('Terminate'))
875
        return form
876

  
877
    def submit_sso_init_link_form(self, form):
878
        fields = [ 'auth_form_places', ]
879
        for f in fields:
880
            setattr(self.host, f, form.get_widget(f).parse())
881
        self.host.auth_form_url = self.host.auth_url
882
        self.generate_apache_filters()
883
        self.host.store()
884
        write_apache2_vhosts()
885

  
886
    def metadatas [html] (self):
887
        form = Form(enctype='multipart/form-data')
888
        form.add_submit('cancel', _('Previous'))
889
        form.add_submit('submit', _('Next'))
890
        form.add_submit('terminate', _('Terminate'))
891

  
892
        if form.get_widget('cancel').parse():
893
            return redirect('sso_init_link')
894

  
895
        if form.is_submitted():
896
            if form.get_widget('terminate').parse():
897
                return redirect('..')
898
            return redirect('advanced_options')
899

  
900
        get_response().breadcrumb.append(('metadatas', _('Metadatas')))
901
        self.html_top(_('Step 8 - Metadatas of %(site_name)s' % {'site_name': self.host.name}))
902

  
903
        '<p>%s</p>' % \
904
            _('''Download the metadatas and the public key for this site and
905
upload them on your identity provider in order to use Liberty Alliance features.''')
906

  
907
        '<dl>'
908
        if hasattr(self.host, str('base_url')):
909
            if lasso.SAML2_SUPPORT:
910
                saml2_metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
911
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
912
                            saml2_metadata_url,
913
                            _('SAML 2.0 Metadata'),
914
                            _('Download SAML 2.0 metadata file'))
915
            metadata_url = '%s/metadata.xml' % self.host.base_url
916
            '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
917
                        metadata_url,
918
                        _('ID-FF 1.2 Metadata'),
919
                        _('Download ID-FF 1.2 metadata file'))
920
        else:
921
            '<p>%s</p>' % _('No metadata has been generated for this host.')
922

  
923
        if hasattr(self.host, str('base_url')) and self.host.public_key and os.path.exists(self.host.public_key):
924
            public_key_url = '%s/public_key' % self.host.base_url
925
            '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
926
                        public_key_url,
927
                        _('Public key'),
928
                        _('Download SSL Public Key file'))
929
        else:
930
            '<p>%s</p>' % _('No public key has been generated for this host.')
931
        '</dl>'
932

  
933
        form.render()
934

  
935
    def advanced_options [html] (self):
936
        form = self.form_advanced_options()
937

  
938
        if form.get_widget('cancel').parse():
939
            return redirect('metadatas')
940

  
941
        if not form.is_submitted() or form.has_errors():
942
            get_response().breadcrumb.append(('advanced_options', _('Advanced options')))
943
            self.html_top(_('Step 9 - Advanced options'))
944

  
945
            '<p>%s</p>' % _('Configure advanced options to setup the last details of your site.')
946
            '<p>%s</p>' % _('''If you don't know what to configure here, just click %(next)s and
947
come here later if needed.''') % {'next': _('Next')}
948

  
949
            form.render()
950
        else:
951
            self.submit_advanced_options_form(form)
952
            if form.get_widget('terminate').parse():
953
                return redirect('..')
954
            return redirect('check_full_configuration')
955

  
956
    def form_advanced_options(self):
957
        form = Form(enctype='multipart/form-data')
958
        form.add(CheckboxWidget, 'redirect_root_to_login',
959
                title=_('Redirect the root URL of the site to the login page.'),
960
                value = self.host.redirect_root_to_login)
961
        form.add(UrlOrAbsPathWidget, 'return_url', title = _('Return address'),
962
                hint = _('Where the user will be redirected after a successful authentication'),
963
                required = False, size = 50, value = self.host.return_url)
964
        form.add(UrlOrAbsPathWidget, 'root_url', title = _('Error address'),
965
                hint = _('Where the user will be redirected after a disconnection or an error'),
966
                required = False, size = 50, value = self.host.root_url)
967
        form.add(UrlOrAbsPathWidget, 'initiate_sso_url', title = _('URL which must initiate the SSO'),
968
                hint = _('''Address which must initiate the SSO. If empty, defaults to the previously
969
specified "%s"''') % _('Authentication form page address'),
970
                required = False, size = 50, value = self.host.initiate_sso_url)
971
        form.add(CheckboxWidget, 'proxy-html', title = _('Apache HTML proxy'),
972
                hint = _('''Converts urls in the HTML pages according to the host new domain name.
973
Disabled by default because it makes some sites not work correctly.'''),
974
                value = 'proxy-html' in self.host.apache_output_filters)
975

  
976
        form.add_submit('cancel', _('Previous'))
977
        form.add_submit('submit', _('Next'))
978
        form.add_submit('terminate', _('Terminate'))
979
        return form
980

  
981
    def submit_advanced_options_form(self, form):
982
        old_redirect_root_to_login = self.host.redirect_root_to_login
983

  
984
        for f in ('redirect_root_to_login', 'return_url', 'root_url', 'initiate_sso_url'):
985
            value = form.get_widget(f).parse()
986
            setattr(self.host, f, value)
987

  
988
        f = 'proxy-html'
989
        value = form.get_widget(f).parse()
990
        if value is True and f not in self.host.apache_output_filters:
991
            self.host.apache_output_filters.append(f)
992
        if value is False and f in self.host.apache_output_filters:
993
            self.host.apache_output_filters.remove(f)
994

  
995
        self.host.store()
996

  
997
        if self.host.initiate_sso_url or self.host.redirect_root_to_login is not old_redirect_root_to_login:
998
            write_apache2_vhosts()
999

  
1000
    def check_full_configuration [html] (self):
1001
        form = Form(enctype='multipart/form-data')
1002
        form.add_submit('cancel', _('Previous'))
1003
        form.add_submit('submit', _('Finish'))
1004

  
1005
        if form.get_widget('cancel').parse():
1006
            return redirect('advanced_options')
1007

  
1008
        if form.is_submitted():
1009
            return redirect('../..')
1010

  
1011
        get_response().breadcrumb.append(('check_full_configuration', _('Check everything works')))
1012
        self.html_top(_('Step 10 - Check everything works'))
1013

  
1014
        '<p>%s</p>' % \
1015
            _('''Now you can fully test your site, start from the home page, initiate a
1016
Single Sign On, federate your identities and do a Single Logout.''')
1017

  
1018
        '<p>%s' % _('The address of your site is : ')
1019
        '<a href="%s">%s</a>' % (self.host.new_url, self.host.new_url)
1020
        '</p>'
1021

  
1022
        '<p>%s</p>' % \
1023
            _('''If everything works, click the "%(finish)s" button, otherwise you can go
1024
back and check your settings.''') % { 'finish': _('Finish') }
1025

  
1026
        form.render()
1027

  
1028
    def auto_detect_configuration(self):
1029
        # Reset previous detected values
1030
        self.host.auth_form = None
1031
        self.host.auth_check_url = None
1032
        self.host.login_field_name = None
1033
        self.host.password_field_name = None
1034
        if not self.host.post_parameters:
1035
            self.host.post_parameters = {}
1036

  
1037
        self.parse_page(self.host.auth_form_url)
1038

  
1039
    def parse_page(self, page_url):
1040
        # Get the authentication page
1041
        try:
1042
            response, status, page, auth_header = http_get_page(page_url, use_proxy=self.host.use_proxy)
1043
        except Exception, msg:
1044
            print msg
1045
            return
1046

  
1047
        if page is None:
1048
            return
1049
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to get page'), self.host.auth_form_url))
1050

  
1051
        # Default authentication mode
1052
        self.host.auth_mode = 'form'
1053

  
1054
        if not self.host.site_authentication_plugin:
1055
            self.host.site_authentication_plugin = site_authentication_plugins.auto_detect(page)
1056
        self.parse_frames(page)
1057
        self.parse_forms(page)
1058
        if self.host.auth_form is not None:
1059
            self.parse_form_action()
1060
            input_fields = self.parse_input_fields()
1061
            self.parse_login_field(input_fields)
1062
            self.parse_password_field()
1063
            self.parse_select_fields()
1064
            self.parse_other_fields()
1065

  
1066
    def parse_frames(self, page):
1067
        '''If there are frames, parse them recursively'''
1068
        regexp = re.compile("""<frame.*?src=["'](.*?)["'][^>]*?>""", re.DOTALL | re.IGNORECASE)
1069
        found_frames = regexp.findall(page)
1070
        if found_frames:
1071
            for frame_url in found_frames:
1072
                if frame_url.startswith('http'):
1073
                    frame_full_url = frame_url
1074
                else:
1075
                    page_url_tokens = frame_url.split('/')
1076
                    page_url_tokens[-1] = frame_url
1077
                    frame_full_url = '/'.join(page_url_tokens)
1078
                self.parse_page(frame_full_url)
1079

  
1080
    def parse_forms(self, page):
1081
        '''Search for an authentication form'''
1082
        # Get all forms
1083
        regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
1084
        found_forms = regexp.findall(page)
1085
        if not found_forms:
1086
            return
1087
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to find any form'), self.host.auth_form_url))
1088

  
1089
        # Get the first form with a password field
1090
        for found_form in found_forms:
1091
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1092
            if regexp.search(found_form) is not None:
1093
                self.host.auth_form = found_form
1094
                break
1095

  
1096
    def parse_form_action(self):
1097
        '''Get the action url of the form'''
1098
        regexp = re.compile("""<form.*?action=["']?(.*?)["']?[\s>].*?>""", re.DOTALL | re.IGNORECASE)
1099
        self.host.auth_form_action = regexp.findall(self.host.auth_form)[0]
1100
        # FIXME: Find a Python module which unescapes html entities
1101
        self.host.auth_check_url = self.host.auth_form_action.replace('&amp;', '&')
1102
        if not self.host.auth_check_url.startswith('http'):
1103
            if self.host.auth_check_url.startswith('/'):
1104
                if self.host.orig_site.startswith('https'):
1105
                    orig_site_root = 'https://%s' % urllib.splithost(self.host.orig_site[6:])[0]
1106
                else:
1107
                    orig_site_root = 'http://%s' % urllib.splithost(self.host.orig_site[5:])[0]
1108
                self.host.auth_check_url = orig_site_root + self.host.auth_check_url
1109
            else:
1110
                auth_form_url_tokens = self.host.auth_form_url.split('/')
1111
                auth_form_url_tokens[-1] = self.host.auth_check_url
1112
                self.host.auth_check_url = '/'.join(auth_form_url_tokens)
1113

  
1114
    def parse_input_fields(self):
1115
        '''Get all input fields'''
1116
        regexp = re.compile("""<input[^>]*?>""", re.DOTALL | re.IGNORECASE)
1117
        return regexp.findall(self.host.auth_form)
1118

  
1119
    def parse_login_field(self, input_fields):
1120
        '''Get login field name'''
1121
        try:
1122
            regexp = re.compile("""<input[^>]*?type=["']?text["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1123
            text_fields = regexp.findall(self.host.auth_form)
1124
            login_field = ''
1125
            if text_fields:
1126
                login_field = text_fields[0]
1127
            else:
1128
                for field in input_fields:
1129
                    if re.search("""type=["']?""", field, re.DOTALL | re.IGNORECASE) is None:
1130
                        login_field = field
1131
                        break
1132
            regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1133
            self.host.login_field_name = regexp.findall(login_field)[0]
1134
            if not self.host.post_parameters.has_key(self.host.login_field_name):
1135
                self.host.post_parameters[self.host.login_field_name] = \
1136
                    { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1137
                self.host.store()
1138
        except IndexError, e:
1139
            self.host.login_field_name = None
1140
            print 'Error handling login field : %s' % e
1141

  
1142
    def parse_password_field(self):
1143
        '''Get password field name'''
1144
        try:
1145
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1146
            password_field = regexp.findall(self.host.auth_form)[0]
1147
            regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1148
            self.host.password_field_name = regexp.findall(password_field)[0]
1149
            if not self.host.post_parameters.has_key(self.host.password_field_name):
1150
                self.host.post_parameters[self.host.password_field_name] = \
1151
                    { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1152
        except IndexError, e:
1153
            self.host.password_field_name = None
1154
            print 'Error handling password field : %s' % e
1155

  
1156
    def parse_select_fields(self):
1157
        '''Add select fields to host attributes'''
1158
        # First added for Imuse (Rennes)
1159
        regexp = re.compile("""<select.*?</select>""", re.DOTALL | re.IGNORECASE)
1160
        self.host.select_fields = {}
1161
        for field in regexp.findall(self.host.auth_form):
1162
            try:
1163
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1164
                name = regexp.findall(field)[0]
1165
                regexp = re.compile("""<option[^>]*?>.*?</option>""", re.DOTALL | re.IGNORECASE)
1166
                options = regexp.findall(field)
1167
                values = []
1168
                for option in options:
1169
                    regexp = re.compile("""<option[^>]*?>(.*?)</option>""", re.DOTALL | re.IGNORECASE)
1170
                    option_label = regexp.findall(option)
1171
                    regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1172
                    option_value = regexp.findall(option)
1173
                    if option_label:
1174
                        if not option_value:
1175
                            option_value = option_label
1176
                        values.append((option_value[0], option_label[0]))
1177
                    else:
1178
                        print >> sys.stderr, 'W: Could not parse select options'
1179
                self.host.select_fields[name] = values
1180
                if not self.host.post_parameters.has_key(name):
1181
                    self.host.post_parameters[name] = \
1182
                        { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1183
            except IndexError, e:
1184
                continue
1185

  
1186
    def parse_other_fields(self):
1187
        '''Get the default value of all other fields'''
1188
        self.host.other_fields = {}
1189

  
1190
        # Get hidden fields
1191
        regexp = re.compile("""<input[^>]*?type=["']?hidden["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1192
        other_fields = regexp.findall(self.host.auth_form)
1193

  
1194
        # Only get first submit field
1195
        regexp = re.compile("""<input[^>]*?type=["']?submit["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1196
        found = regexp.findall(self.host.auth_form)
1197
        if found:
1198
            if other_fields:
1199
                other_fields.append(found[0])
1200
            else:
1201
                other_fields = found[0]
1202

  
1203
        for field in other_fields:
1204
            try:
1205
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1206
                name = regexp.findall(field)[0]
1207
                regexp = re.compile("""value=["'](.*?)["'][\s/>]""", re.DOTALL | re.IGNORECASE)
1208
                value = regexp.findall(field)[0]
1209
                self.host.other_fields[name] = value
1210
                if not self.host.post_parameters.has_key(name):
1211
                    self.host.post_parameters[name] = { 'enabled': True, 'value': value, 'immutable': False }
1212
            except IndexError, e:
1213
                continue
1214

  
1215

  
1216
class HostPage(Directory):
1217
    _q_exports = ['', 'delete']
1218

  
1219
    def __init__(self, host_id):
1220
        self.host = Host.get(host_id)
1221
        get_response().breadcrumb.append((host_id + '/', self.host.label))
1222

  
1223
    def _q_lookup(self, component):
1224
        if component == 'configuration_assistant':
1225
            return ConfigurationAssistant(self.host)
1226
        elif component == 'forms_prefill':
1227
            return FormsDirectory(self.host)
1228

  
1229
    def _q_index [html] (self):
1230
        get_publisher().reload_cfg()
1231
        html_top('hosts', title = self.host.label)
1232

  
1233
        '<h2>%s</h2>' % _('Configuration assistant')
1234

  
1235
        '<dl>'
1236

  
1237
        '<dt><a href="configuration_assistant/start">%s</a></dt> <dd>%s</dd>' % (
1238
                _('Address of the original site'), _('Configure the root address of the site'))
1239

  
1240
        '<dt><a href="configuration_assistant/check_new_address">%s</a></dt> <dd>%s</dd>' % (
1241
                _('New address and name'), _('Configure the new address and name of this site'))
1242

  
1243
        '<dt><a href="configuration_assistant/authentication_and_logout_adresses">%s</a></dt> <dd>%s</dd>' % (
1244
                _('Authentication and logout addresses'), _('Configure the authentication and logout addresses of the original site'))
1245

  
1246
        '<dt><a href="configuration_assistant/check_auto_detected_configuration">%s</a></dt> <dd>%s</dd>' % (
1247
                _('Check auto detected configuration'), _('Check the automatically detected configuration is right'))
1248

  
1249
        '<dt><a href="configuration_assistant/credentials">%s</a></dt> <dd>%s</dd>' % (
1250
                _('Credentials'), _('Configure some valid credentials to authenticate on the original site'))
1251

  
1252
        '<dt><a href="configuration_assistant/send_authentication_request">%s</a></dt> <dd>%s</dd>' % (
1253
                _('Retry authentication'), _('Retry sending an authentication request to the site to check if your new parameters work well'))
1254

  
1255
        '<dt><a href="configuration_assistant/see_authentication_response">%s</a></dt> <dd>%s</dd>' % (
1256
                _('Check authentication response'), _('Check the response from the latest authentication request'))
1257

  
1258
        '<dt><a href="configuration_assistant/authentication_success_criteria">%s</a></dt> <dd>%s</dd>' % (
1259
                _('Configure authentication success criteria'), _('Specify how Larpe knows if the authentication has succeeded or not'))
1260

  
1261
        '<dt><a href="configuration_assistant/modify_authentication_request">%s</a></dt> <dd>%s</dd>' % (
1262
                _('Modify authentication request'), _('Modify POST fields or HTTP headers of the authentication request'))
1263

  
1264
        '<dt><a href="configuration_assistant/sso_init_link">%s</a></dt> <dd>%s</dd>' % (
1265
                _('Configure how a Single Sign On can be initiated'), _('Configure how a Single Sign On can be initiated'))
1266

  
1267
        '<dt><a href="configuration_assistant/metadatas">%s</a></dt> <dd>%s</dd>' % (
1268
                _('Metadatas and key'), _('Download SAML 2.0 or ID-FF metadatas and SSL public key'))
1269

  
1270
        '<dt><a href="configuration_assistant/advanced_options">%s</a></dt> <dd>%s</dd>' % (
1271
                _('Adavanced options'), _('Configure advanced options to setup the last details of your site'))
1272

  
1273
        '</dl>'
1274

  
1275
        '<h2>%s</h2>' % _('Form prefilling with ID-WSF')
1276

  
1277
        '<dl>'
1278
        '<dt><a href="forms_prefill/">%s</a></dt> <dd>%s</dd>' % (
1279
                _('Forms'), _('Configure the forms to prefill'))
1280
        '</dl>'
1281

  
1282
    def delete [html] (self):
1283
        form = Form(enctype='multipart/form-data')
1284
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
1285
                        'You are about to irrevocably delete this host.')))
1286
        form.add_submit('submit', _('Submit'))
1287
        form.add_submit('cancel', _('Cancel'))
1288
        if form.get_widget('cancel').parse():
1289
            return redirect('..')
1290
        if not form.is_submitted() or form.has_errors():
1291
            get_response().breadcrumb.append(('delete', _('Delete')))
1292
            html_top('hosts', title = _('Delete Host'))
1293
            '<h2>%s : %s</h2>' % (_('Delete Host'), self.host.label)
1294
            form.render()
1295
        else:
1296
            self.host.remove_self()
1297
            write_apache2_vhosts()
1298
            return redirect('..')
1299

  
1300

  
1301
class HostsDirectory(Directory):
1302
    _q_exports = ['', 'new']
1303

  
1304
    def _q_index [html] (self):
1305
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1306
        html_top('hosts', title = _('Hosts'))
1307
        """<ul id="nav-hosts-admin">
1308
          <li><a href="new">%s</a></li>
1309
        </ul>""" % _('New Host')
1310

  
1311
        '<ul class="biglist">'
1312

  
1313
        for host in Host.select(lambda x: x.name != 'larpe', order_by = 'label'):
1314
            if not host.name:
1315
                continue
1316
            if not hasattr(host, str('scheme')):
1317
                host.scheme = str('http')
1318
            '<li>'
1319
            '<strong class="label">%s</strong>' % host.label
1320
            if hasattr(host, str('new_url')) and host.new_url:
1321
                url = host.new_url
1322
            else:
1323
                # Compat with older Larpe versions
1324
                url = '%s://%s%s/' % (host.scheme, host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
1325
                if host.reversed_directory is not None:
1326
                    url += '%s/' % host.reversed_directory
1327
            '<br /><a href="%s">%s</a>' % (url, url)
1328
            '<p class="commands">'
1329
            command_icon('%s/' % host.id, 'edit')
1330
            command_icon('%s/delete' % host.id, 'remove')
1331
            '</p></li>'
1332
        '</ul>'
1333

  
1334
    def new [html] (self):
1335
        if not os.path.isdir(os.path.join(get_publisher().app_dir, str('idp'))):
1336
            html_top('hosts', title = _('New Host'))
1337
            html = '<h2>%s</h2>' % _('New Host')
1338
            html += 'You must <a href="%s/admin/settings/liberty_idp/">' % misc.get_root_url()
1339
            html += 'configure an Identity Provider</a> first<br /><br />'
1340
            html += '<a href="."><input type="button" value="%s" /></a>' % _('Back')
1341
            return html
1342

  
1343
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1344
        get_response().breadcrumb.append(('new', _('New')) )
1345
        host = Host()
1346
        host.store()
1347
        return redirect('%s/configuration_assistant/start' % host.id)
1348

  
1349
    def _q_lookup(self, component):
1350
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1351
        return HostPage(component)
1352

  
larpe/admin/liberty_utils.py
1
import os
2

  
3
def set_provider_keys(private_key_path, public_key_path):
4
    # use system calls for openssl since PyOpenSSL doesn't expose the
5
    # necessary functions.
6
    if os.system('openssl version > /dev/null 2>&1') == 0:
7
        os.system('openssl genrsa -out %s 2048' % private_key_path)
8
        os.system('openssl rsa -in %s -pubout -out %s' % (private_key_path, public_key_path))
9

  
10

  
11
def get_metadata(cfg):
12
    prologue = """\
13
<?xml version="1.0"?>
14
<EntityDescriptor
15
    providerID="%(provider_id)s"
16
    xmlns="urn:liberty:metadata:2003-08">""" % cfg
17

  
18
    sp_head = """
19
  <SPDescriptor protocolSupportEnumeration="urn:liberty:iff:2003-08">"""
20

  
21
    signing_public_key = ''
22
    if cfg.has_key('signing_public_key') and cfg['signing_public_key']:
23
        if 'CERTIF' in cfg['signing_public_key']:
24
            signing_public_key = """
25
        <KeyDescriptor use="signing">
26
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
27
            <ds:X509Data><ds:X509Certificate>%s</ds:X509Certificate></ds:X509Data>
28
          </ds:KeyInfo>
29
        </KeyDescriptor>""" % cfg['signing_public_key']
30
        elif 'KEY' in cfg['signing_public_key']:
31
            signing_public_key = """
32
        <KeyDescriptor use="signing">
33
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
34
            <ds:KeyValue>%s</ds:KeyValue>
35
          </ds:KeyInfo>
36
        </KeyDescriptor>""" % cfg['signing_public_key']
37

  
38
    sp_body = """
39
    <AssertionConsumerServiceURL id="AssertionConsumerServiceURL1" isDefault="true">%(base_url)s/assertionConsumer</AssertionConsumerServiceURL>
40

  
41
    <SoapEndpoint>%(base_url)s/soapEndpoint</SoapEndpoint>
42

  
43
    <SingleLogoutServiceURL>%(base_url)s/singleLogout</SingleLogoutServiceURL>
44
    <SingleLogoutServiceReturnURL>%(base_url)s/singleLogoutReturn</SingleLogoutServiceReturnURL>
45
    <SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-idp-http</SingleLogoutProtocolProfile>
46
    <SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-sp-soap</SingleLogoutProtocolProfile>
47
    <SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-sp-http</SingleLogoutProtocolProfile>
48

  
49
    <FederationTerminationServiceURL>%(base_url)s/federationTermination</FederationTerminationServiceURL>
50
    <FederationTerminationServiceReturnURL>%(base_url)s/federationTerminationReturn</FederationTerminationServiceReturnURL>
51
    <FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-idp-soap</FederationTerminationNotificationProtocolProfile>
52
    <FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-idp-http</FederationTerminationNotificationProtocolProfile>
53
    <FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-sp-soap</FederationTerminationNotificationProtocolProfile>
54
    <FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-sp-http</FederationTerminationNotificationProtocolProfile>
55

  
56
    <AuthnRequestsSigned>true</AuthnRequestsSigned>
57

  
58
  </SPDescriptor>""" % cfg
59

  
60
    orga = ''
61
    if cfg.get('organization_name'):
62
        orga = """
63
  <Organization>
64
    <OrganizationName>%s</OrganizationName>
65
  </Organization>""" % unicode(cfg['organization_name'], 'iso-8859-1').encode('utf-8')
66

  
67
    epilogue = """
68
</EntityDescriptor>"""
69

  
70
    return '\n'.join([prologue, sp_head, signing_public_key, sp_body, orga, epilogue])
71

  
72

  
73

  
74
def get_saml2_metadata(cfg):
75
    prologue = """\
76
<?xml version="1.0"?>
77
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
78
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
79
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
80
    entityID="%(saml2_provider_id)s">""" % cfg
81

  
82
    sp_head = """
83
  <SPSSODescriptor
84
      AuthnRequestsSigned="true"
85
      protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">"""
86

  
87
    signing_public_key = ''
88
    if cfg.has_key('signing_public_key') and cfg['signing_public_key']:
89
        if 'CERTIF' in cfg['signing_public_key']:
90
            signing_public_key = """
91
        <KeyDescriptor use="signing">
92
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
93
            <ds:X509Data><ds:X509Certificate>%s</ds:X509Certificate></ds:X509Data>
94
          </ds:KeyInfo>
95
        </KeyDescriptor>""" % cfg['signing_public_key']
96
        elif 'KEY' in cfg['signing_public_key']:
97
            signing_public_key = """
98
        <KeyDescriptor use="signing">
99
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
100
            <ds:KeyValue>%s</ds:KeyValue>
101
          </ds:KeyInfo>
102
        </KeyDescriptor>""" % cfg['signing_public_key']
103

  
104
    sp_body = """
105
    <AssertionConsumerService isDefault="true" index="0"
106
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
107
      Location="%(saml2_base_url)s/singleSignOnArtifact" />
108
    <SingleLogoutService
109
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
110
      Location="%(saml2_base_url)s/singleLogoutSOAP" />
111
    <SingleLogoutService
112
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
113
      Location="%(saml2_base_url)s/singleLogout"
114
      ResponseLocation="%(saml2_base_url)s/singleLogoutReturn" />
115

  
116
  </SPSSODescriptor>""" % cfg
117

  
118
    orga = ''
119
    if cfg.get('organization_name'):
120
        orga = """
121
  <Organization>
122
    <OrganizationName>%s</OrganizationName>
123
  </Organization>""" % unicode(cfg['organization_name'], 'iso-8859-1').encode('utf-8')
124

  
125
    epilogue = """
126
</EntityDescriptor>"""
127

  
128
    return '\n'.join([prologue, sp_head, signing_public_key, sp_body, orga, epilogue])
129

  
larpe/admin/root.ptl
1
import os
2

  
3
import lasso
4

  
5
from quixote import get_session, get_session_manager, get_publisher, get_request, get_response
6
from quixote.directory import Directory, AccessControlled
7

  
8
from qommon.admin.menu import html_top
9
from qommon.admin import logger
10

  
11
from larpe import errors
12
from larpe import misc
13

  
14
import hosts
15
import users
16
import settings
17

  
18
def gpl [html] ():
19
    """<p>This program is free software; you can redistribute it and/or modify it
20
    under the terms of the GNU General Public License as published by the Free
21
    Software Foundation; either version 2 of the License, or (at your option)
22
    any later version.</p>
23

  
24
    <p>This program is distributed in the hope that it will be useful, but
25
    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
26
    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
27
    for more details.</p>
28

  
29
    <p>You should have received a copy of the GNU General Public License along with
30
    this program; if not, write to the Free Software Foundation, Inc., 59 Temple
31
    Place - Suite 330, Boston, MA  02111-1307, USA.</p>
32
    """
33

  
34

  
35
class RootDirectory(AccessControlled, Directory):
36
    _q_exports = ['', 'hosts', 'users', 'settings', 'logger']
37

  
38
    hosts = hosts.HostsDirectory()
39
    users = users.UsersDirectory()
40
    settings = settings.SettingsDirectory()
41
    logger = logger.LoggerDirectory()
42

  
43
    menu_items = [
44
        ('hosts/', N_('Hosts')),
45
        ('users/', N_('Users')),
46
        ('settings/', N_('Settings')),
47
        ('logger/', N_('Logs')),
48
        ('/', N_('Liberty Alliance Reverse Proxy'))]
49

  
50
    def _q_access(self):
51
        # FIXME : this block should be moved somewhere else
52
        get_publisher().reload_cfg()
53
        if not get_publisher().cfg.has_key('proxy_hostname'):
54
            get_publisher().cfg['proxy_hostname'] = get_request().get_server().split(':')[0]
55
        get_publisher().write_cfg()
56

  
57
        response = get_response()
58
        if not hasattr(response, 'breadcrumb'):
59
            response.breadcrumb = [ ('../admin/', _('Administration')) ]
60

  
61
        # Cheater
62
        if os.path.exists(os.path.join(get_publisher().app_dir, 'ADMIN_FOR_ALL')):
63
            return
64

  
65
        # No admin user created yet, free access
66
        user_list = users.User.select(lambda x: x.is_admin)
67
        if not user_list:
68
            return
69

  
70
        host_list = hosts.Host.select(lambda x: x.name == 'larpe')
71
        if host_list:
72
            host = host_list[0]
73
        else:
74
            raise errors.AccessForbiddenError()
75

  
76
        if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
77
            user = get_session().get_user(host.saml2_provider_id)
78
        else:
79
            user = get_session().get_user(host.provider_id)
80
        if user:
81
            if not user.name or not user.is_admin:
82
                raise errors.AccessForbiddenError()
83
        else:
84
            raise errors.AccessUnauthorizedError()
85

  
86

  
87
    def _q_index [html] (self):
88
        html_top('')
89
        gpl()
90

  
larpe/admin/settings.ptl
1
import cStringIO
2
import cPickle
3
import re
4
import os
5
import lasso
6
import glob
7
import zipfile
8

  
9
from quixote import get_publisher, get_request, get_response, redirect
10
from quixote.directory import Directory, AccessControlled
11

  
12
from qommon.form import *
13
from qommon.misc import get_abs_path
14
from qommon.admin.cfg import cfg_submit
15
from qommon.admin.menu import html_top, error_page
16
from qommon.admin.emails import EmailsDirectory as QommonEmailsDirectory
17
from qommon.admin.settings import SettingsDirectory as QommonSettingsDirectory
18

  
19
from larpe import misc
20
from larpe.hosts import Host
21
from larpe.admin.liberty_utils import *
22

  
23
class LibertyIDPDir(Directory):
24
    _q_exports = ['', ('metadata.xml', 'metadata')]
25

  
26
    def _q_index [html] (self):
27
        form = Form(enctype="multipart/form-data")
28
        form.add(FileWidget, "metadata", title = _("Metadata"), required=True)
29
        form.add(FileWidget, "publickey", title = _("Public Key"), required=False)
30
        form.add(FileWidget, "cacertchain", title = _("CA Certificate Chain"), required=False)
31
        form.add_submit("submit", _("Submit"))
32

  
33
        if not form.is_submitted() or form.has_errors():
34
            html_top('settings', title = _('New Identity Provider'))
35
            "<h2>%s</h2>" % _('New Identity Provider')
36
            form.render()
37
        else:
38
            self.submit_new(form)
39

  
40
    def submit_new(self, form, key_provider_id = None):
41
        metadata, publickey, cacertchain = None, None, None
42
        if form.get_widget('metadata').parse():
43
            metadata = form.get_widget('metadata').parse().fp.read()
44
        if form.get_widget('publickey').parse():
45
            publickey = form.get_widget('publickey').parse().fp.read()
46
        if form.get_widget('cacertchain').parse():
47
            cacertchain = form.get_widget('cacertchain').parse().fp.read()
48

  
49
        if not key_provider_id:
50
            try:
51
                provider_id = re.findall(r'(provider|entity)ID="(.*?)"', metadata)[0][1]
52
            except IndexError:
53
                return error_page('settings', _('Bad metadata'))
54
            key_provider_id = provider_id.replace(str('://'), str('-')).replace(str('/'), str('-'))
55

  
56
        dir = get_abs_path(os.path.join('idp', key_provider_id))
57
        if not os.path.isdir(dir):
58
            os.makedirs(dir)
59

  
60
        if metadata:
61
            metadata_fn = os.path.join(dir, 'metadata.xml')
62
            open(metadata_fn, 'w').write(metadata)
63
        if publickey:
64
            publickey_fn = os.path.join(dir, 'public_key')
65
            open(publickey_fn, 'w').write(publickey)
66
        else:
67
            publickey_fn = None
68
        if cacertchain:
69
            cacertchain_fn = os.path.join(dir, 'ca_cert_chain.pem')
70
            open(cacertchain_fn, 'w').write(cacertchain)
71
        else:
72
            cacertchain_fn = None
73

  
74
        p = lasso.Provider(lasso.PROVIDER_ROLE_IDP, metadata_fn, publickey_fn, None)
75

  
76
        try:
77
            misc.get_provider_label(p)
78
            get_publisher().cfg['idp'] = key_provider_id
79
            get_publisher().write_cfg()
80
        except TypeError:
81
            if metadata:
82
                os.unlink(metadata_fn)
83
            if publickey:
84
                os.unlink(publickey_fn)
85
            if cacertchain:
86
                os.unlink(cacertchain_fn)
87
            return error_page('settings', _('Bad metadata'))
88

  
89
        redirect('..')
90

  
91
    def metadata(self):
92
        response = get_response()
93
        response.set_content_type('text/xml', 'utf-8')
94
        get_publisher().reload_cfg()
95
        if get_publisher().cfg['idp']:
96
            idp_metadata = os.path.join(get_abs_path('idp'), get_publisher().cfg['idp'], 'metadata.xml')
97
            return unicode(open(idp_metadata).read(), 'utf-8')
98
        return 'No IDP is configured'
99

  
100

  
101
class EmailsDirectory(QommonEmailsDirectory):
102
    def _q_index [html] (self):
103
        # Don't use custom emails
104
        html_top('settings', title = _('Emails'))
105
        '<h2>%s</h2>' % _('Emails')
106

  
107
        '<ul>'
108
        '<li><a href="options">%s</a></li>' % _('General Options')
109
        '</ul>'
110

  
111
        '<p>'
112
        '<a href="..">%s</a>' % _('Back')
113
        '</p>'
114

  
115

  
116
class SettingsDirectory(QommonSettingsDirectory):
117
    _q_exports = ['', 'liberty_sp', 'liberty_idp', 'domain_names', 'apache2_configuration_generation',
118
                  'proxy', 'language', 'emails', 'debug_options' ]
119

  
120
    liberty_idp = LibertyIDPDir()
121
    emails = EmailsDirectory()
122

  
123
    def _q_index [html] (self):
124
        get_publisher().reload_cfg()
125
        html_top('settings', title = _('Settings'))
126

  
127
        if lasso.SAML2_SUPPORT:
128
            '<h2>%s</h2>' % _('Liberty Alliance & SAML 2.0 Service Provider')
129
        else:
130
            '<h2>%s</h2>' % _('Liberty Alliance Service Provider')
131
        '<dl> <dt><a href="liberty_sp">%s</a></dt> <dd>%s</dd>' % (
132
                _('Service Provider'), _('Configure Larpe as a Service Provider'))
133

  
134
        hosts = Host.select(lambda x: x.name == 'larpe')
135
        if hosts:
136
            self.host = hosts[0]
137

  
138
            if lasso.SAML2_SUPPORT and self.host.saml2_metadata is not None:
139
                metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
140
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
141
                        metadata_url,
142
                        _('SAML 2.0 Metadata'),
143
                        _('Download SAML 2.0 metadata file for Larpe'))
144

  
145
            if self.host.metadata is not None:
146
                metadata_url = '%s/metadata.xml' % self.host.base_url
147
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
148
                        metadata_url,
149
                        _('ID-FF 1.2 Metadata'),
150
                        _('Download ID-FF 1.2 metadata file for Larpe'))
151

  
152
            if self.host.public_key is not None:
153
                public_key_url = '%s/public_key' % self.host.base_url
154
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
155
                        public_key_url,
156
                        _('Public key'),
157
                        _('Download SSL Public Key file'))
158

  
159
        if lasso.SAML2_SUPPORT:
160
            '<h2>%s</h2>' % _('Liberty Alliance & SAML 2.0 Identity Provider')
161
        else:
162
            '<h2>%s</h2>' % _('Liberty Alliance Identity Provider')
163

  
164
        '<dl>'
165

  
166
        '<dt><a href="liberty_idp/">%s</a></dt> <dd>%s</dd>' % (
167
               _('Identity Provider'), _('Configure an identity provider'))
168

  
169
        if get_publisher().cfg.has_key('idp'):
170
            '<dt><a href="liberty_idp/metadata.xml">%s</a></dt> <dd>%s</dd>' % (
171
                    _('Identity Provider metadatas'), _('See current identity provider metadatas'))
172

  
173
        '</dl>'
174

  
175
        '<h2>%s</h2>' % _('Global parameters for the sites')
176

  
177
        '<dl>'
178
        '<dt><a href="domain_names">%s</a></dt> <dd>%s</dd>' % (
179
                _('Domain name'), _('Configure the base domain name for the sites'))
180
        '<dt><a href="apache2_configuration_generation">%s</a></dt> <dd>%s</dd>' % (
181
                _('Apache 2 configuration generation'), _('Customise Apache 2 configuration generation'))
182
        '<dt><a href="proxy">%s</a></dt> <dd>%s</dd>' % (
183
                _('Proxy'), _('Connect to the sites through a web proxy'))
184
        '</dl>'
185

  
186
        '<h2>%s</h2>' % _('Customisation')
187

  
188
        '<dl>'
189
        '<dt><a href="language">%s</a></dt> <dd>%s</dd>' % (
190
                _('Language'), _('Configure site language'))
191
        '<dt><a href="emails/">%s</a></dt> <dd>%s</dd>' % (
192
                _('Emails'), _('Configure email settings'))
193
        '</dl>'
194

  
195
        '<h2>%s</h2>' % _('Misc')
196

  
197
        '<dl>'
198
        '<dt><a href="debug_options">%s</a></dt> <dd>%s</dd>' % (
199
                _('Debug Options'), _('Configure options useful for debugging'))
200
        '</dl>'
201

  
202

  
203
    def liberty_sp [html] (self):
204
        get_publisher().reload_cfg()
205

  
206
        # Get the host object for the reverse proxy
207
        hosts = Host.select(lambda x: x.name == 'larpe')
208
        if hosts:
209
            self.host = hosts[0]
210
        else:
211
            self.host = Host()
212
            self.host.reversed_hostname = get_publisher().cfg[str('proxy_hostname')]
213

  
214
        form = Form(enctype='multipart/form-data')
215
        form.add(StringWidget, 'organization_name', title=_('Organisation Name'), size=50,
216
                required = True, value = self.host.organization_name)
217
        form.add_submit('submit', _('Submit'))
218
        form.add_submit('cancel', _('Cancel'))
219
        if form.get_widget('cancel').parse():
220
            return redirect('.')
221
        if not form.is_submitted() or form.has_errors():
222
            html_top('settings', title = _('Service Provider Configuration'))
223
            '<h2>%s</h2>' % _('Service Provider Configuration')
224
            form.render()
225
        else:
226
            self.liberty_sp_submit(form)
227
            redirect('.')
228

  
229
    def liberty_sp_submit(self, form):
230
        get_publisher().reload_cfg()
231
        metadata_cfg = {}
232

  
233
        f = 'organization_name'
234
        if form.get_widget(f):
235
            setattr(self.host, f, form.get_widget(f).parse())
236

  
237
        metadata_cfg['organization_name'] = self.host.organization_name
238

  
239
        self.host.name = 'larpe'
240

  
241
        # Liberty Alliance / SAML parameters
242
        base_url = '%s/liberty/%s/liberty' % (misc.get_root_url(), self.host.name)
243
        metadata_cfg['base_url'] = base_url
244
        self.host.base_url = base_url
245

  
246
        if lasso.SAML2_SUPPORT:
247
            saml2_base_url = '%s/liberty/%s/saml' % (misc.get_root_url(), self.host.name)
248
            metadata_cfg['saml2_base_url'] = saml2_base_url
249
            self.host.saml2_base_url = saml2_base_url
250

  
251
        provider_id = '%s/metadata' % base_url
252
        metadata_cfg['provider_id'] = provider_id
253
        self.host.provider_id = provider_id
254

  
255
        if lasso.SAML2_SUPPORT:
256
            saml2_provider_id = '%s/metadata' % saml2_base_url
257
            metadata_cfg['saml2_provider_id'] = saml2_provider_id
258
            self.host.saml2_provider_id = saml2_provider_id
259

  
260
        # Storage directories
261
        site_dir = os.path.join(get_publisher().app_dir, 'sp',
262
                    self.host.reversed_hostname, self.host.name)
263
        user_dir = os.path.join(site_dir, 'users')
264
        token_dir = os.path.join(site_dir, 'tokens')
265
        for dir in (site_dir, user_dir, token_dir):
266
            if not os.path.isdir(dir):
267
                os.makedirs(dir)
268
        metadata_cfg['site_dir'] = site_dir
269
        self.host.site_dir = site_dir
270

  
271
        # Generate SSL keys
272
        private_key_path = os.path.join(site_dir, 'private_key.pem')
273
        public_key_path = os.path.join(site_dir, 'public_key')
274
        if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
275
            set_provider_keys(private_key_path, public_key_path)
276
        self.host.private_key = private_key_path
277
        metadata_cfg['signing_public_key'] = open(public_key_path).read()
278
        self.host.public_key = public_key_path
279

  
280
        # Write metadatas
281
        metadata_path = os.path.join(site_dir, 'metadata.xml')
282
        open(metadata_path, 'w').write(get_metadata(metadata_cfg))
283
        self.host.metadata = metadata_path
284

  
285
        if hasattr(self.host, 'saml2_provider_id'):
286
            saml2_metadata_path = os.path.join(site_dir, 'saml2_metadata.xml')
287
            open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
288
            self.host.saml2_metadata = saml2_metadata_path
289

  
290
        self.host.root_url = '%s/' % misc.get_root_url()
291
        self.host.return_url = '%s/admin/' % misc.get_root_url()
292

  
293
        self.host.store()
294

  
295
    def domain_names [html] (self):
296
        form = self.form_domain_name()
297

  
298
        if form.get_widget('cancel').parse():
299
            return redirect('.')
300

  
301
        if not form.is_submitted() or form.has_errors():
302
            html_top('settings', title = _('Domain name'))
303
            '<h2>%s</h2>' % _('Domain name')
304
            form.render()
305
        else:
306
            self.submit_domain_name(form)
307
            redirect('.')
308

  
309
    def form_domain_name(self):
310
        get_publisher().reload_cfg()
311
        if get_cfg('domain_names'):
312
            domain_name = get_cfg('domain_names')[0]
313
        else:
314
            domain_name = None
315

  
316
        form = Form(enctype='multipart/form-data')
317
        form.add(StringWidget, 'domain_name',
318
            title=_('Domain name for the sites'),
319
            value = domain_name)
320
        # TODO: Add the option "Both" and handle it in hosts configuration
321
        form.add(SingleSelectWidget, 'sites_url_scheme', title = _('Use HTTP or HTTPS'),
322
                value = get_cfg('sites_url_scheme'),
323
                options = [ (None, _('Same as the site')),
324
                            ('http', 'HTTP'),
325
                            ('https', 'HTTPS') ] )
326
        form.add_submit('submit', _('Submit'))
327
        form.add_submit('cancel', _('Cancel'))
328
        return form
329

  
330
    def submit_domain_name(self, form):
331
        get_publisher().reload_cfg()
332
        get_publisher().cfg['domain_names'] = [ form.get_widget('domain_name').parse() ]
333
        get_publisher().cfg['sites_url_scheme'] = form.get_widget('sites_url_scheme').parse()
334
        get_publisher().write_cfg()
335

  
336
    def apache2_configuration_generation [html] (self):
337
        get_publisher().reload_cfg()
338

  
339
        form = Form(enctype='multipart/form-data')
340
        form.add(CheckboxWidget, 'allow_config_generation',
341
                title=_('Automatically generate Apache 2 configuration for new hosts and reload Apache 2 after changes'),
342
                value = get_publisher().cfg.get(str('allow_config_generation'), True))
343
        form.add_submit('submit', _('Submit'))
344
        form.add_submit('cancel', _('Cancel'))
345
        if form.get_widget('cancel').parse():
346
            return redirect('.')
347
        if not form.is_submitted() or form.has_errors():
348
            html_top('settings', title = _('Apache 2 configuration generation'))
349
            '<h2>%s</h2>' % _('Apache 2 configuration generation')
350
            form.render()
351
        else:
352
            self.apache2_configuration_generation_submit(form)
353
            redirect('.')
354

  
355
    def apache2_configuration_generation_submit(self, form):
356
        get_publisher().reload_cfg()
357

  
358
        f = 'allow_config_generation'
359
        get_publisher().cfg[f] = form.get_widget(f).parse()
360

  
361
        get_publisher().write_cfg()
larpe/admin/users.ptl
1
import random
2

  
3
import lasso
4

  
5
from quixote import get_request, get_session, redirect, get_publisher
6
from quixote.directory import Directory
7

  
8
from qommon.admin.menu import html_top, error_page, command_icon
9
from qommon.errors import EmailError
10
from qommon.form import *
11
from qommon import emails
12

  
13
from larpe import errors
14
from larpe import misc
15
from larpe.users import User
16
from larpe.hosts import Host
17

  
18
class UserUI:
19
    def __init__(self, user):
20
        self.user = user
21

  
22
    def form_new(self):
23
        form = Form(enctype="multipart/form-data")
24
        form.add(StringWidget, "name", title = _('User Name'), required = True, size=30)
25
        form.add(StringWidget, "email", title = _('Email'), required = False, size=30)
26
        form.add_submit("submit", _("Submit"))
27
        form.add_submit("cancel", _("Cancel"))
28
        return form
29

  
30
    def form_edit(self):
31
        form = Form(enctype="multipart/form-data")
32
        form.add(StringWidget, "name", title = _('User Name'), required = True, size=30,
33
                value = self.user.name)
34
        form.add(StringWidget, "email", title = _('Email'), required = False, size=30,
35
                value = self.user.email)
36
        form.add_submit("submit", _("Submit"))
37
        form.add_submit("cancel", _("Cancel"))
38
        return form
39

  
40
    def submit_form(self, form):
41
        if not self.user:
42
            self.user = User()
43
        for f in ('name', 'email'):
44
            widget = form.get_widget(f)
45
            if widget:
46
                setattr(self.user, f, widget.parse())
47
        self.user.is_admin = True
48
        self.user.store()
49

  
50

  
51
class UserPage(Directory):
52
    _q_exports = ['', 'edit', 'delete', 'token']
53

  
54
    def __init__(self, component):
55
        self.user = User.get(component)
56
        self.user_ui = UserUI(self.user)
57
        get_response().breadcrumb.append((component + '/', self.user.name))
58

  
59
    def _q_index [html] (self):
60
        html_top('users', '%s - %s' % (_('User'), self.user.name))
61
        '<h2>%s - %s</h2>' % (_('User'), self.user.name)
62
        '<div class="form">'
63
        '<div class="title">%s</div>' % _('Name')
64
        '<div class="StringWidget content">%s</div>' % self.user.name
65
        if self.user.email:
66
            '<div class="title">%s</div>' % _('Email')
67
            '<div class="StringWidget content">%s</div>' % self.user.email
68
#         if self.user.lasso_dump:
69
#             identity = lasso.Identity.newFromDump(self.user.lasso_dump)
70
#             server = misc.get_lasso_server()
71
#             if len(identity.providerIds) and server:
72
#                 '<h3>%s</h3>' % _('Liberty Alliance Details')
73
#                 '<div class="StringWidget content"><ul>'
74
#                 for pid in identity.providerIds:
75
#                     provider = server.getProvider(pid)
76
#                     label = misc.get_provider_label(provider)
77
#                     if label:
78
#                         label = '%s (%s)' % (label, pid)
79
#                     else:
80
#                         label = pid
81
#                     federation = identity.getFederation(pid)
82
#                     '<li>'
83
#                     _('Account federated with %s') % label
84
#                     '<br />'
85
#                     if federation.localNameIdentifier:
86
#                         _("local: ") + federation.localNameIdentifier.content
87
#                     if federation.remoteNameIdentifier:
88
#                         _("remote: ") + federation.remoteNameIdentifier.content
89
#                     '</li>'
90
#                 '</ul></div>'
91

  
92
#                 # XXX: only display this in debug mode:
93
#                 '<h4>%s</h4>' % _('Lasso Identity Dump')
94
#                 '<pre>%s</pre>' % self.user.lasso_dump
95
        '</div>'
96

  
97
    def debug [html] (self):
98
        get_response().breadcrumb.append( ('debug', _('Debug')) )
99
        html_top('users', 'Debug')
100
        "<h2>Debug - %s</h2>" % self.user.name
101
        "<pre>"
102
        self.user.lasso_dump
103
        "</pre>"
104

  
105
    def edit [html] (self):
106
        form = self.user_ui.form_edit()
107
        if form.get_widget('cancel').parse():
108
            return redirect('..')
109
        if not form.is_submitted() or form.has_errors():
110
            get_response().breadcrumb.append( ('edit', _('Edit')) )
111
            html_top('users', title = _('Edit User'))
112
            '<h2>%s</h2>' % _('Edit User')
113
            form.render()
114
        else:
115
            self.user_ui.submit_form(form)
116
            return redirect('..')
117

  
118
    def delete [html] (self):
119
        form = Form(enctype="multipart/form-data")
120
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
121
                        "You are about to irrevocably delete this user.")))
122
        form.add_submit("submit", _("Submit"))
123
        form.add_submit("cancel", _("Cancel"))
124
        if form.get_widget('cancel').parse():
125
            return redirect('..')
126
        if not form.is_submitted() or form.has_errors():
127
            get_response().breadcrumb.append(('delete', _('Delete')))
128
            html_top('users', title = _('Delete User'))
129
            '<h2>%s %s</h2>' % (_('Deleting User :'), self.user.name)
130
            form.render()
131
        else:
132
            self.user.remove_self()
133
            return redirect('..')
134

  
135
    def token [html] (self):
136
        form = Form(enctype="multipart/form-data", use_tokens = False)
137
        form.add_submit("submit", _("Generate"))
138
        form.add_submit("cancel", _("Cancel"))
139
        request = get_request()
140
        if request.form.has_key('cancel') or request.form.has_key('done'):
141
            return redirect('..')
142

  
143
        get_response().breadcrumb.append(('token', _('Identification Token')))
144

  
145
        if not form.is_submitted() or form.has_errors():
146
            html_top('users', title = _('Identification Token'))
147
            '<h2>%s</h2>' % _('Identification Token')
148
            '<p>%s</p>' % _('You are about to generate a token than can be used to federate the account.')
149
            '<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.')
150
            if self.user.identification_token:
151
                '<p>%s</p>' % _('Note that user has already been issued an identification token : %s') % self.user.identification_token
152
            form.render()
153
        else:
154
            if request.form.has_key('submit'):
155
                html_top('users', title = _('Identification Token'))
156
                token = '-'.join(['%04d' % random.randint(1, 9999) for x in range(4)])
157
                self.user.identification_token = str(token)
158
                self.user.store()
159

  
160
                '<p>'
161
                _('Identification Token for %s') % self.user.name
162
                ' : %s</p>' % self.user.identification_token
163

  
164
                form = Form(enctype="multipart/form-data", use_tokens = False)
165
                form.add_submit('done', _('Done'))
166
                if self.user.email:
167
                    form.add_submit("submit-email", _("Send by email"))
168
                form.render()
169
            else:
170
                site_url = '%s://%s%s/token?token=%s' \
171
                    % (request.get_scheme(), request.get_server(),
172
                       get_request().environ['SCRIPT_NAME'], self.user.identification_token)
173
                body = _("""You have been given an identification token.
174

  
175
Your token is %(token)s
176

  
177
Click on %(url)s to use it.
178
""") % {'token': self.user.identification_token, 'url': site_url}
179
                try:
180
                    emails.email(_('Identification Token'), body, self.user.email)
181
                except EmailError, e:
182
                    html_top('users', title = _('Identification Token'))
183
                    _('Failed sending email. Check your email configuration.')
184
                    '<div class="buttons"><a href=".."><input type="button" value="%s" /></a></div><br />' % _('Back')
185
                else:
186
                    return redirect('..')
187

  
188
class UsersDirectory(Directory):
189

  
190
    _q_exports = ['', 'new']
191

  
192
    def _q_index [html] (self):
193
        get_publisher().reload_cfg()
194
        get_response().breadcrumb.append( ('users/', _('Users')) )
195
        html_top('users', title = _('Users'))
196

  
197

  
198
        if not list(Host.select(lambda x: x.name == 'larpe')):
199
            '<p>%s</p>' % _('Liberty support must be setup before creating users.')
200
        else:
201
            """<ul id="nav-users-admin">
202
              <li><a href="new">%s</a></li>
203
            </ul>""" % _('New User')
204

  
205
        debug_cfg = get_publisher().cfg.get('debug', {})
206

  
207
        users = User.select(lambda x: x.name is not None, order_by = 'name')
208

  
209
        '<ul class="biglist">'
210
        for user in users:
211
            '<li>'
212
            '<strong class="label">%s</strong>' % user.name
213
            if user.email:
214
                '<p class="details">'
215
                user.email
216
                '</p>'
217

  
218
            '<p class="commands">'
219
            command_icon('%s/' % user.id, 'view')
220
            if not user.name_identifiers:
221
                if not user.identification_token:
222
                    command_icon('%s/token' % user.id, 'token',
223
                            label = _('Identification Token'), icon = 'stock_exec_16.png')
224
                else:
225
                    command_icon('%s/token' % user.id, 'token',
226
                            label = _('Identification Token (current: %s)') % \
227
                                user.identification_token,
228
                            icon = 'stock_exec_16.png')
229
            command_icon('%s/edit' % user.id, 'edit')
230
            command_icon('%s/delete' % user.id, 'remove')
231
            if debug_cfg.get('logger', False):
232
                command_icon('../logger/by_user/%s/' % user.id, 'logs',
233
                        label = _('Logs'), icon = 'stock_harddisk_16.png')
234
            '</p></li>'
235
        '</ul>'
236

  
237
    def new [html] (self):
238
        get_response().breadcrumb.append( ('users/', _('Users')) )
239
        get_response().breadcrumb.append( ('new', _('New')) )
240
        hosts = list(Host.select(lambda x: x.name == 'larpe'))
241
        if not hosts:
242
            return error_page('users', _('Liberty support must be setup before creating users.'))
243
        host = hosts[0]
244
        # XXX: user must be logged in to get here
245
        user_ui = UserUI(None)
246
        # FIXME : should be able to use User.count(). Track fake user creations.
247
        users = User.select(lambda x: x.name is not None)
248
        first_user = (len(users) == 0)
249
        form = user_ui.form_new()
250
        if form.get_widget('cancel').parse():
251
            return redirect('.')
252

  
253
        if not form.is_submitted() or form.has_errors():
254
            html_top('users', title = _('New User'))
255
            '<h2>%s</h2>' % _('New User')
256
            form.render()
257
        else:
258
            user_ui.submit_form(form)
259
            if first_user:
260
                session = get_session()
261
                if hasattr(session, str('lasso_dump')):
262
                    user_ui.user.name_identifiers = [ session.name_identifier ]
263
                    user_ui.user.lasso_dumps = [ session.lasso_anonymous_identity_dump ]
264
                    user_ui.user.store()
265
                if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
266
                    get_session().set_user(user_ui.user.id, host.saml2_provider_id)
267
                else:
268
                    get_session().set_user(user_ui.user.id, host.provider_id)
269
            return redirect('.')
270

  
271
    def _q_lookup(self, component):
272
        get_response().breadcrumb.append( ('users/', _('Users')) )
273
        try:
274
            return UserPage(component)
275
        except KeyError:
276
            raise errors.TraversalError()
larpe/branches/idwsf/AUTHORS
1
Damien Laniel <dlaniel@entrouvert.com>
2

  
3
Artwork and administrave interface design taken from DotClear, version 1.2 and
4
2.0, released under the GNU General Public License; and GTK+, version 2.8,
5
released under the GNU Lesser General Public License.
larpe/branches/idwsf/COPYING
1
		    GNU GENERAL PUBLIC LICENSE
2
		       Version 2, June 1991
3

  
4
 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
 Everyone is permitted to copy and distribute verbatim copies
7
 of this license document, but changing it is not allowed.
8

  
9
			    Preamble
10

  
11
  The licenses for most software are designed to take away your
12
freedom to share and change it.  By contrast, the GNU General Public
13
License is intended to guarantee your freedom to share and change free
14
software--to make sure the software is free for all its users.  This
15
General Public License applies to most of the Free Software
16
Foundation's software and to any other program whose authors commit to
17
using it.  (Some other Free Software Foundation software is covered by
18
the GNU Lesser General Public License instead.)  You can apply it to
19
your programs, too.
20

  
21
  When we speak of free software, we are referring to freedom, not
22
price.  Our General Public Licenses are designed to make sure that you
23
have the freedom to distribute copies of free software (and charge for
24
this service if you wish), that you receive source code or can get it
25
if you want it, that you can change the software or use pieces of it
26
in new free programs; and that you know you can do these things.
27

  
28
  To protect your rights, we need to make restrictions that forbid
29
anyone to deny you these rights or to ask you to surrender the rights.
30
These restrictions translate to certain responsibilities for you if you
31
distribute copies of the software, or if you modify it.
32

  
33
  For example, if you distribute copies of such a program, whether
34
gratis or for a fee, you must give the recipients all the rights that
35
you have.  You must make sure that they, too, receive or can get the
36
source code.  And you must show them these terms so they know their
37
rights.
38

  
39
  We protect your rights with two steps: (1) copyright the software, and
40
(2) offer you this license which gives you legal permission to copy,
41
distribute and/or modify the software.
42

  
43
  Also, for each author's protection and ours, we want to make certain
44
that everyone understands that there is no warranty for this free
45
software.  If the software is modified by someone else and passed on, we
46
want its recipients to know that what they have is not the original, so
47
that any problems introduced by others will not reflect on the original
48
authors' reputations.
49

  
50
  Finally, any free program is threatened constantly by software
51
patents.  We wish to avoid the danger that redistributors of a free
52
program will individually obtain patent licenses, in effect making the
53
program proprietary.  To prevent this, we have made it clear that any
54
patent must be licensed for everyone's free use or not licensed at all.
55

  
56
  The precise terms and conditions for copying, distribution and
57
modification follow.
58

  
59
		    GNU GENERAL PUBLIC LICENSE
60
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61

  
62
  0. This License applies to any program or other work which contains
63
a notice placed by the copyright holder saying it may be distributed
64
under the terms of this General Public License.  The "Program", below,
65
refers to any such program or work, and a "work based on the Program"
66
means either the Program or any derivative work under copyright law:
67
that is to say, a work containing the Program or a portion of it,
68
either verbatim or with modifications and/or translated into another
69
language.  (Hereinafter, translation is included without limitation in
70
the term "modification".)  Each licensee is addressed as "you".
71

  
72
Activities other than copying, distribution and modification are not
73
covered by this License; they are outside its scope.  The act of
74
running the Program is not restricted, and the output from the Program
75
is covered only if its contents constitute a work based on the
76
Program (independent of having been made by running the Program).
77
Whether that is true depends on what the Program does.
78

  
79
  1. You may copy and distribute verbatim copies of the Program's
80
source code as you receive it, in any medium, provided that you
81
conspicuously and appropriately publish on each copy an appropriate
82
copyright notice and disclaimer of warranty; keep intact all the
83
notices that refer to this License and to the absence of any warranty;
84
and give any other recipients of the Program a copy of this License
85
along with the Program.
86

  
87
You may charge a fee for the physical act of transferring a copy, and
88
you may at your option offer warranty protection in exchange for a fee.
89

  
90
  2. You may modify your copy or copies of the Program or any portion
91
of it, thus forming a work based on the Program, and copy and
92
distribute such modifications or work under the terms of Section 1
93
above, provided that you also meet all of these conditions:
94

  
95
    a) You must cause the modified files to carry prominent notices
96
    stating that you changed the files and the date of any change.
97

  
98
    b) You must cause any work that you distribute or publish, that in
99
    whole or in part contains or is derived from the Program or any
100
    part thereof, to be licensed as a whole at no charge to all third
101
    parties under the terms of this License.
102

  
103
    c) If the modified program normally reads commands interactively
104
    when run, you must cause it, when started running for such
105
    interactive use in the most ordinary way, to print or display an
106
    announcement including an appropriate copyright notice and a
107
    notice that there is no warranty (or else, saying that you provide
108
    a warranty) and that users may redistribute the program under
109
    these conditions, and telling the user how to view a copy of this
110
    License.  (Exception: if the Program itself is interactive but
111
    does not normally print such an announcement, your work based on
112
    the Program is not required to print an announcement.)
113

  
114
These requirements apply to the modified work as a whole.  If
115
identifiable sections of that work are not derived from the Program,
116
and can be reasonably considered independent and separate works in
117
themselves, then this License, and its terms, do not apply to those
118
sections when you distribute them as separate works.  But when you
119
distribute the same sections as part of a whole which is a work based
120
on the Program, the distribution of the whole must be on the terms of
121
this License, whose permissions for other licensees extend to the
122
entire whole, and thus to each and every part regardless of who wrote it.
123

  
124
Thus, it is not the intent of this section to claim rights or contest
125
your rights to work written entirely by you; rather, the intent is to
126
exercise the right to control the distribution of derivative or
127
collective works based on the Program.
128

  
129
In addition, mere aggregation of another work not based on the Program
130
with the Program (or with a work based on the Program) on a volume of
131
a storage or distribution medium does not bring the other work under
132
the scope of this License.
133

  
134
  3. You may copy and distribute the Program (or a work based on it,
135
under Section 2) in object code or executable form under the terms of
136
Sections 1 and 2 above provided that you also do one of the following:
137

  
138
    a) Accompany it with the complete corresponding machine-readable
139
    source code, which must be distributed under the terms of Sections
140
    1 and 2 above on a medium customarily used for software interchange; or,
141

  
142
    b) Accompany it with a written offer, valid for at least three
143
    years, to give any third party, for a charge no more than your
144
    cost of physically performing source distribution, a complete
145
    machine-readable copy of the corresponding source code, to be
146
    distributed under the terms of Sections 1 and 2 above on a medium
147
    customarily used for software interchange; or,
148

  
149
    c) Accompany it with the information you received as to the offer
150
    to distribute corresponding source code.  (This alternative is
151
    allowed only for noncommercial distribution and only if you
152
    received the program in object code or executable form with such
153
    an offer, in accord with Subsection b above.)
154

  
155
The source code for a work means the preferred form of the work for
156
making modifications to it.  For an executable work, complete source
157
code means all the source code for all modules it contains, plus any
158
associated interface definition files, plus the scripts used to
159
control compilation and installation of the executable.  However, as a
160
special exception, the source code distributed need not include
161
anything that is normally distributed (in either source or binary
162
form) with the major components (compiler, kernel, and so on) of the
163
operating system on which the executable runs, unless that component
164
itself accompanies the executable.
165

  
166
If distribution of executable or object code is made by offering
167
access to copy from a designated place, then offering equivalent
168
access to copy the source code from the same place counts as
169
distribution of the source code, even though third parties are not
170
compelled to copy the source along with the object code.
171

  
172
  4. You may not copy, modify, sublicense, or distribute the Program
173
except as expressly provided under this License.  Any attempt
174
otherwise to copy, modify, sublicense or distribute the Program is
175
void, and will automatically terminate your rights under this License.
176
However, parties who have received copies, or rights, from you under
177
this License will not have their licenses terminated so long as such
178
parties remain in full compliance.
179

  
180
  5. You are not required to accept this License, since you have not
181
signed it.  However, nothing else grants you permission to modify or
182
distribute the Program or its derivative works.  These actions are
183
prohibited by law if you do not accept this License.  Therefore, by
184
modifying or distributing the Program (or any work based on the
185
Program), you indicate your acceptance of this License to do so, and
186
all its terms and conditions for copying, distributing or modifying
187
the Program or works based on it.
188

  
189
  6. Each time you redistribute the Program (or any work based on the
190
Program), the recipient automatically receives a license from the
191
original licensor to copy, distribute or modify the Program subject to
192
these terms and conditions.  You may not impose any further
193
restrictions on the recipients' exercise of the rights granted herein.
194
You are not responsible for enforcing compliance by third parties to
195
this License.
196

  
197
  7. If, as a consequence of a court judgment or allegation of patent
198
infringement or for any other reason (not limited to patent issues),
199
conditions are imposed on you (whether by court order, agreement or
200
otherwise) that contradict the conditions of this License, they do not
201
excuse you from the conditions of this License.  If you cannot
202
distribute so as to satisfy simultaneously your obligations under this
203
License and any other pertinent obligations, then as a consequence you
204
may not distribute the Program at all.  For example, if a patent
205
license would not permit royalty-free redistribution of the Program by
206
all those who receive copies directly or indirectly through you, then
207
the only way you could satisfy both it and this License would be to
208
refrain entirely from distribution of the Program.
209

  
210
If any portion of this section is held invalid or unenforceable under
211
any particular circumstance, the balance of the section is intended to
212
apply and the section as a whole is intended to apply in other
213
circumstances.
214

  
215
It is not the purpose of this section to induce you to infringe any
216
patents or other property right claims or to contest validity of any
217
such claims; this section has the sole purpose of protecting the
218
integrity of the free software distribution system, which is
219
implemented by public license practices.  Many people have made
220
generous contributions to the wide range of software distributed
221
through that system in reliance on consistent application of that
222
system; it is up to the author/donor to decide if he or she is willing
223
to distribute software through any other system and a licensee cannot
224
impose that choice.
225

  
226
This section is intended to make thoroughly clear what is believed to
227
be a consequence of the rest of this License.
228

  
229
  8. If the distribution and/or use of the Program is restricted in
230
certain countries either by patents or by copyrighted interfaces, the
231
original copyright holder who places the Program under this License
232
may add an explicit geographical distribution limitation excluding
233
those countries, so that distribution is permitted only in or among
234
countries not thus excluded.  In such case, this License incorporates
235
the limitation as if written in the body of this License.
236

  
237
  9. The Free Software Foundation may publish revised and/or new versions
238
of the General Public License from time to time.  Such new versions will
239
be similar in spirit to the present version, but may differ in detail to
240
address new problems or concerns.
241

  
242
Each version is given a distinguishing version number.  If the Program
243
specifies a version number of this License which applies to it and "any
244
later version", you have the option of following the terms and conditions
245
either of that version or of any later version published by the Free
246
Software Foundation.  If the Program does not specify a version number of
247
this License, you may choose any version ever published by the Free Software
248
Foundation.
249

  
250
  10. If you wish to incorporate parts of the Program into other free
251
programs whose distribution conditions are different, write to the author
252
to ask for permission.  For software which is copyrighted by the Free
253
Software Foundation, write to the Free Software Foundation; we sometimes
254
make exceptions for this.  Our decision will be guided by the two goals
255
of preserving the free status of all derivatives of our free software and
256
of promoting the sharing and reuse of software generally.
257

  
258
			    NO WARRANTY
259

  
260
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
262
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
266
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
267
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
REPAIR OR CORRECTION.
269

  
270
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
POSSIBILITY OF SUCH DAMAGES.
279

  
280
		     END OF TERMS AND CONDITIONS
281

  
282
	    How to Apply These Terms to Your New Programs
283

  
284
  If you develop a new program, and you want it to be of the greatest
285
possible use to the public, the best way to achieve this is to make it
286
free software which everyone can redistribute and change under these terms.
287

  
288
  To do so, attach the following notices to the program.  It is safest
289
to attach them to the start of each source file to most effectively
290
convey the exclusion of warranty; and each file should have at least
291
the "copyright" line and a pointer to where the full notice is found.
292

  
293
    <one line to give the program's name and a brief idea of what it does.>
294
    Copyright (C) <year>  <name of author>
295

  
296
    This program is free software; you can redistribute it and/or modify
297
    it under the terms of the GNU General Public License as published by
298
    the Free Software Foundation; either version 2 of the License, or
299
    (at your option) any later version.
300

  
301
    This program is distributed in the hope that it will be useful,
302
    but WITHOUT ANY WARRANTY; without even the implied warranty of
303
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
304
    GNU General Public License for more details.
305

  
306
    You should have received a copy of the GNU General Public License along
307
    with this program; if not, write to the Free Software Foundation, Inc.,
308
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309

  
310
Also add information on how to contact you by electronic and paper mail.
311

  
312
If the program is interactive, make it output a short notice like this
313
when it starts in an interactive mode:
314

  
315
    Gnomovision version 69, Copyright (C) year name of author
316
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317
    This is free software, and you are welcome to redistribute it
318
    under certain conditions; type `show c' for details.
319

  
320
The hypothetical commands `show w' and `show c' should show the appropriate
321
parts of the General Public License.  Of course, the commands you use may
322
be called something other than `show w' and `show c'; they could even be
323
mouse-clicks or menu items--whatever suits your program.
324

  
325
You should also get your employer (if you work as a programmer) or your
326
school, if any, to sign a "copyright disclaimer" for the program, if
327
necessary.  Here is a sample; alter the names:
328

  
329
  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330
  `Gnomovision' (which makes passes at compilers) written by James Hacker.
331

  
332
  <signature of Ty Coon>, 1 April 1989
333
  Ty Coon, President of Vice
334

  
335
This General Public License does not permit incorporating your program into
336
proprietary programs.  If your program is a subroutine library, you may
337
consider it more useful to permit linking proprietary applications with the
338
library.  If this is what you want to do, use the GNU Lesser General
339
Public License instead of this License.
larpe/branches/idwsf/MANIFEST.in
1
include Makefile
2
include setup.py
3
include larpectl
4
include apache2-vhost-larpe
5
include apache2.conf
6
include larpe-reload-apache2-script
7
include larpe-reload-apache2.c
8
include README COPYING MANIFEST.in MANIFEST NEWS AUTHORS
9
recursive-include larpe *.py *.ptl
10
recursive-include data *
11
recursive-include root *
12
recursive-include po *.po *.pot Makefile
13
recursive-include doc *.rst Makefile *.png *.sty custom.tex *.py *.sh *.css
larpe/branches/idwsf/Makefile
1
prefix = /usr
2
config_prefix = /var
3
config_dir = $(config_prefix)/lib/larpe
4

  
5
INSTALL = /usr/bin/install -c
6
PYTHON = /usr/bin/python
7

  
8
LARPE_USER = www-data
9
LARPE_GROUP = www-data
10
APACHE_INIT_SCRIPT = /etc/init.d/apache2
11

  
12
larpe-reload-apache2: larpe-reload-apache2.c
13

  
14
install: larpe-reload-apache2
15
	rm -rf build
16
	$(MAKE) -C po install
17
	$(PYTHON) setup.py install --root "$(DESTDIR)/" --prefix "$(prefix)"
18
	$(INSTALL) -d $(DESTDIR)$(prefix)/sbin/
19
	$(INSTALL) larpectl $(DESTDIR)$(prefix)/sbin/
20
	$(INSTALL) larpe-reload-apache2-script $(DESTDIR)$(prefix)/sbin/
21
	$(INSTALL) -m 4550 --group $(LARPE_GROUP) larpe-reload-apache2 $(DESTDIR)$(prefix)/sbin/
22
	$(INSTALL) -d $(DESTDIR)$(prefix)/share/larpe/
23
	$(INSTALL) -m 0644 apache2.conf $(DESTDIR)$(prefix)/share/larpe/
24
	chown -R $(LARPE_USER):$(LARPE_GROUP) /usr/share/larpe/
25

  
26
	$(INSTALL) --owner=$(LARPE_USER) --group=$(LARPE_GROUP) -d $(DESTDIR)$(config_dir)
27
	$(INSTALL) --owner=$(LARPE_USER) --group=$(LARPE_GROUP) -d $(DESTDIR)$(config_dir)/vhosts.d
28
	$(INSTALL) --owner=$(LARPE_USER) --group=$(LARPE_GROUP) -d $(DESTDIR)$(config_dir)/vhost-locations.d
29
	$(INSTALL) --owner=$(LARPE_USER) --group=$(LARPE_GROUP) -d $(DESTDIR)$(config_dir)/vhosts.d.disabled
30
	$(INSTALL) --owner=$(LARPE_USER) --group=$(LARPE_GROUP) -d $(DESTDIR)$(config_dir)/vhost-locations.d.disabled
31

  
32
uninstall:
33
	$(MAKE) -C po uninstall
34
	-rm -f $(DESTDIR)$(prefix)/sbin/larpe-reload-apache2
35
	-rm -f $(DESTDIR)$(prefix)/sbin/larpe-reload-apache2-script
36
	-rm -f $(DESTDIR)$(prefix)/sbin/larpectl
37
	-rm -rf $(DESTDIR)$(prefix)/share/larpe/
38
	@echo
39
	@echo "Depending on your Python version, you will have to remove manually the files in /usr/lib/python(your_version)/site-packages/larpe/"
40

  
41
clean:
42
	$(MAKE) -C po clean
43
	$(MAKE) -C doc clean
44
	-$(PYTHON) setup.py clean
45
	-rm larpe-reload-apache2
46

  
larpe/branches/idwsf/NEWS
1
NEWS
2
====
larpe/branches/idwsf/README
1
Larpe - Liberty Alliance Reverse Proxy
2
======================================
3

  
4
Description
5
-----------
6

  
7
Larpe is a Liberty Alliance Reverse Proxy.  It allow any service provider (that
8
is a website) to use Liberty Alliance features (Identity federation, Single
9
Sign On and Single Sign Logout) without changing the code of the service
10
provider itself.  It uses the Lasso library which is certified by the Liberty
11
Alliance consortium.
12

  
13

  
14
Documentation
15
-------------
16

  
17
* README, as you are doing;
18

  
19
* doc/en/ for English documentation, published on the website as
20
  http://larpe.labs.libre-entreprise.org/doc/en/larpe-admin.html
21

  
22

  
23
Copyright
24
---------
25

  
26
Larpe is copyrighted by Entr'ouvert and is licensed through the GNU General
27
Public Licence.  Artwork and administrative design are from DotClear and
28
released under the GNU General Public License by Olivier Meunier and others.
29
Some artwork comes from GTK+ (LGPL).
30

  
31
Read the COPYING file for the complete license text.  Read the AUTHORS file for
32
additional credits.
33

  
larpe/branches/idwsf/TODO
1
- Tests
2
  - egroupware
3
  - http://labs.libre-entreprise.org/
4
  - logs.entrouvert.org
5

  
6
====== Roadmap de Larpe ======
7

  
8
===== 0.2 =====
9

  
10
  * Vérifier la compatibilité avec egroupware
11
  * 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
12
  * Supprimer debconf
13
  * Vérification des formulaires de configuration d'hôtes
14
    * Tests de valeurs erronés diverses
15
    * Erreur si on donne un label qui existe deja
16
  * Ne plus inclure le binaire larpe-reload-apache2 dans les sources
17
  * Corriger les avertissements debian
18
  * Ne pas demander la clé publique de l'idp
19
  * Compléter les traductions
20
  * Ajouter la possibilité de changer la langue dans l'interface d'administration
21
  * Mettre à jour la documentation
22
    * Ajouter un chapitre sur les sites testés et leurs options de configuration particulières
23
  * Traduire la documentation en français
24

  
25
===== 0.3 =====
26

  
27
  * Implémenter le SLO en SOAP
28
  * Supprimer un /liberty/ des urls
29
  * Voir comment activer le SSLProxyEngine quand on utilise un sous répertoire
30
  * Faire un site web pour présenter Larpe
31
  * Ajouter la possibilité d'envoyer les exceptions par courriel à l'administrateur
32
  * Améliorer la journalisation des accès et des erreurs
33

  
34
===== 1.0 =====
35

  
36
  * Support de SAML 2.0
37
  * Implémenter l'accès à un site nécessitant une authentification préalable avant tout accès
38
    * Choix de cette fonctionnalité par une option de configuration par site
39
  * 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
40
  * Documentation technique pour les développeurs ?
41

  
42
===== Non classés =====
43

  
44
  * Support des sites qui ont une authentification HTTP (à priori, nécessite de charger toute la configuration de larpe dans le filtre python d'apache)
45
  * Création de nouveaux comptes pour les sites, avec des jetons (déjà implémenté en partie ; est-ce utile ?)
46

  
47
Fait
48
====
49

  
50
- Serveur python principal
51
  - Fonctionnalités liberty
52
    - SSO (depuis le sp et depuis l'idp)
53
    - Fédération
54
    - SLO (depuis le sp et depuis l'idp)
55
    - Défédération (depuis l'idp) en SOAP et redirect
56
  - Support https
57
  - Possibilité d'utiliser toutes les combinaisons de sous domaines et de sous répertoires
58
    - RP par vhost (appli1.example.com, rp de appli1.interne)
59
    - RP par repertoire (www.example.com/appli1, rp de appl1.interne)
60
  - Récupère la configuration de l'IP des vhosts
61

  
62
- Administration
63
  - Authentification liberty sur l'admin
64
  - Créer de nouveaux sites (+ modifier, supprimer)
65
  - Écrire les vhosts correspondants
66
  - Rechargement de la configuration d'apache
67
    - Script + wrapper en C suid root
68
  - Gestion d'utilisateurs pour administrer le RP (Authentification http)
69
  - Gestion des traductions
70

  
71
- Filtre Python branché en sortie sur Apache à la suite du filtre de réécriture html (proxy_html)
72
  - Générique
73
  - Personalisable par site pour une meilleure intégration dans les pages
74

  
75
- Sites testés
76
  - Dotclear
77
  - Linuxfr
78
  - listes.entrouvert.com
79
  - https://listes.libre-entreprise.org/  
80
  - all4dev.libre-entreprise.org
81
  - www.libre-entreprise.org
82
  - http://www.besancon.com/
83
  - quintine.entrouvert.org/egroupware/
84
  - squirrelmail
85

  
86
- Documentation
87

  
88
- Paquets Debian
89
  - Debconf pour demander le nom de domaine et le courriel de l'admin, ainsi que le compte administrateur
90

  
91
- Installation sur lupin
92

  
93
- Batterie de tests de non-regression
94

  
larpe/branches/idwsf/apache2-vhost-larpe
1
<VirtualHost *>
2
    ServerName localhost
3
    ServerAdmin root@localhost
4

  
5
    include /usr/share/larpe/apache2.conf
6
    include /var/lib/larpe/vhost-locations.d
7

  
8
    CustomLog /var/log/apache2/larpe-access.log combined
9
    ErrorLog /var/log/apache2/larpe-error.log
10
</VirtualHost>
11

  
12
include /var/lib/larpe/vhosts.d
larpe/branches/idwsf/apache2.conf
1
# Static files
2
DocumentRoot /usr/share/larpe/web/
3

  
4
# Python application
5
SCGIMount / 127.0.0.1:3007
6

  
7
# Don't change static files
8
<Location /css/>
9
    SCGIHandler off
10
</Location>
11
<Location /images/>
12
    SCGIHandler off
13
</Location>
14
<Location /js/>
15
    SCGIHandler off
16
</Location>
17

  
18
<Location /larpe/>
19
    ProxyPass   !
20
    SCGIHandler off
21
</Location>
22

  
23
<Location /liberty/>
24
    ProxyPass   !
25
</Location>
26

  
27
# No gzip compression
28
RequestHeader unset Accept-Encoding
29

  
30
# HTML url rewriting module and customized Python module
31
SetOutputFilter OURFILTER
larpe/branches/idwsf/debian/changelog
1
larpe (0.2.1-1) unstable; urgency=low
2

  
3
  * New release
4

  
5
 -- Damien Laniel <dlaniel@entrouvert.com>  Wed, 20 Jun 2007 15:43:16 +0200
6

  
7
larpe (0.2.0-1) unstable; urgency=low
8

  
9
  * New release
10

  
11
 -- Damien Laniel <dlaniel@entrouvert.com>  Tue, 30 Jan 2007 18:07:04 +0100
12

  
13
larpe (0.1.1-2) unstable; urgency=low
14

  
15
  * Use python2.4
16

  
17
 -- Damien Laniel <dlaniel@entrouvert.com>  Tue, 19 Dec 2006 17:21:05 +0100
18

  
19
larpe (0.1.1-1) unstable; urgency=low
20

  
21
  * New release
22

  
23
 -- Damien Laniel <dlaniel@entrouvert.com>  Thu,  5 Oct 2006 11:47:53 +0200
24

  
25
larpe (0.1.0-1) unstable; urgency=low
26

  
27
  * New release
28

  
29
 -- Damien Laniel <dlaniel@entrouvert.com>  Wed,  4 Oct 2006 10:19:26 +0200
30

  
31
larpe (0.0.4-1) unstable; urgency=low
32

  
33
  * New version, many improvements, more compatible sites, some bug fixes
34

  
35
 -- Damien Laniel <dlaniel@entrouvert.com>  Tue,  3 Oct 2006 20:44:06 +0200
36

  
37
larpe (0.0.3-1) unstable; urgency=low
38

  
39
  * New version, many improvements, more compatible sites, some bug fixes
40

  
41
 -- Damien Laniel <dlaniel@entrouvert.com>  Mon, 25 Sep 2006 11:11:36 +0200
42

  
43
larpe (0.0.2-1) unstable; urgency=low
44

  
45
  * Initial package.
46

  
47
 -- Damien Laniel <dlaniel@entrouvert.com>  Fri, 08 Sep 2006 16:00:00 +0200
48

  
larpe/branches/idwsf/debian/compat
1
5
larpe/branches/idwsf/debian/config
1
#!/bin/sh -e
2

  
3
# Source debconf library.
4
. /usr/share/debconf/confmodule
5

  
6
# Hostname
7
#db_input high larpe/hostname || true
8
#db_go
9

  
10
# Administrator email address
11
#db_input medium larpe/admin_email || true
12
#db_go
13

  
14
# Enable this vhost
15
#db_input high larpe/enable_vhost || true
16
#db_go
17

  
18
# Administrator login
19
#db_input high larpe/admin_username || true
20
#db_go
21

  
22
# Administrator password
23
#db_input high larpe/admin_password || true
24
#db_go
larpe/branches/idwsf/debian/control
1
Source: larpe
2
Section: web
3
Priority: optional
4
Maintainer: Damien Laniel <dlaniel@entrouvert.com>
5
Build-Depends: debhelper (>= 5.0.37.2), python, python-central (>= 0.5), gettext
6
Standards-Version: 3.7.2.0
7
XS-Python-Version: current
8

  
9
Package: larpe
10
Architecture: any
11
XB-Python-Version: ${python:Versions}
12
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
13
Description: Liberty Alliance Reverse Proxy
14
 Larpe allows any service provider (that is a website) to use Liberty Alliance
15
 identity management and Single Sign On features without changing the code of
16
 the service provider itself.
17
 .
larpe/branches/idwsf/debian/copyright
1
This package was debianized by Damien Laniel <dlaniel@entrouvert.com> on
2
Fri, 08 Sep 2006 16:00:00 +0200.
3

  
4
Upstream Author: Damien Laniel <dlaniel@entrouvert.com>
5

  
6
Copyright (c) 2005 Entr'ouvert;
7
copyright (c) 2003-2005 dotclear for some graphics.
8

  
9
License is GNU GPL v2 or later plus OpenSSL exception clause.
10

  
11
This program is free software; you can redistribute it and/or
12
modify it under the terms of the GNU General Public License
13
as published by the Free Software Foundation; either version 2
14
of the License, or (at your option) any later version.
15

  
16
This program is distributed in the hope that it will be useful,
17
but WITHOUT ANY WARRANTY; without even the implied warranty of
18
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
GNU General Public License for more details.
20

  
21
You should have received a copy of the GNU General Public License
22
along with this program; if not, write to the Free Software
23
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
24

  
25
On Debian GNU/Linux systems, the complete text of the GNU General Public
26
License can be found in `/usr/share/common-licenses/GPL'.
27

  
larpe/branches/idwsf/debian/dirs
1
etc/apache2/sites-available
2
usr/sbin
3
var/lib/larpe
larpe/branches/idwsf/debian/docs
1
README
larpe/branches/idwsf/debian/init
1
#! /bin/sh
2
### BEGIN INIT INFO
3
# Provides:          larpe
4
# Required-Start:    $local_fs $network
5
# Required-Stop:     $local_fs $network
6
# Default-Start:     2 3 4 5
7
# Default-Stop:      0 1 6
8
# Short-Description: Start Larpe Liberty Alliance reverse proxy
9
# Description:       Start Larpe Liberty Alliance reverse proxy
10
### END INIT INFO
11

  
12
set -e
13

  
14
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
15
DESC="larpe"
16
NAME=larpe
17
DAEMON=/usr/sbin/larpectl
18
PIDFILE=/var/run/$NAME.pid
19
SCRIPTNAME=/etc/init.d/$NAME
20

  
21
# Gracefully exit if the package has been removed.
22
test -x $DAEMON || exit 0
23

  
24
. /lib/lsb/init-functions
25

  
26
# Read config file if it is present.
27
if [ -r /etc/default/$NAME ]
28
then
29
    . /etc/default/$NAME
30
fi
31

  
32
#
33
#	Function that starts the daemon/service.
34
#
35
d_start() {
36
    start-stop-daemon --start --quiet --pidfile $PIDFILE --oknodo \
37
		--chuid www-data:www-data --make-pidfile --background --exec $DAEMON -- start $OPTIONS
38
}
39

  
40
#
41
#	Function that stops the daemon/service.
42
#
43
d_stop() {
44
    start-stop-daemon --stop --quiet --pidfile $PIDFILE --oknodo
45
    rm -f $PIDFILE
46
}
47

  
48
#
49
#	Function that sends a SIGHUP to the daemon/service.
50
#
51
d_reload() {
52
    start-stop-daemon --stop --quiet --pidfile $PIDFILE \
53
		--make-pidfile --background --signal 1
54
}
55

  
56
case "$1" in
57
    start)
58
        log_begin_msg "Starting $DESC: $NAME"
59
        d_start
60
        log_end_msg $?
61
        ;;
62

  
63
    stop)
64
        log_begin_msg "Stopping $DESC: $NAME"
65
        d_stop
66
        log_end_msg $?
67
        ;;
68

  
69
    #reload)
70
    #
71
    #	If the daemon can reload its configuration without
72
    #	restarting (for example, when it is sent a SIGHUP),
73
    #	then implement that here.
74
    #
75
    #	If the daemon responds to changes in its config file
76
    #	directly anyway, make this an "exit 0".
77
    #
78
    # echo -n "Reloading $DESC configuration..."
79
    # d_reload
80
    # echo "done."
81
    #;;
82

  
83
    restart|force-reload)
84
    #
85
    #	If the "reload" option is implemented, move the "force-reload"
86
    #	option to the "reload" entry above. If not, "force-reload" is
87
    #	just the same as "restart".
88
    #
89
        log_begin_msg "Restarting $DESC: $NAME"
90
        d_stop
91
        sleep 1
92
        d_start
93
        log_end_msg $?
94
        ;;
95
    
96
    *)
97
        # echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
98
        echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
99
        exit 1
100
        ;;
101
esac
102

  
103
exit 0
larpe/branches/idwsf/debian/postinst
1
#! /bin/sh
2
# postinst script for larpe
3
#
4
# see: dh_installdeb(1)
5

  
6
set -e
7

  
8
# summary of how this script can be called:
9
#        * <postinst> `configure' <most-recently-configured-version>
10
#        * <old-postinst> `abort-upgrade' <new version>
11
#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
12
#          <new-version>
13
#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
14
#          <failed-install-package> <version> `removing'
15
#          <conflicting-package> <version>
16
# for details, see http://www.debian.org/doc/debian-policy/ or
17
# the debian-policy package
18
#
19
# quoting from the policy:
20
#     Any necessary prompting should almost always be confined to the
21
#     post-installation script, and should be protected with a conditional
22
#     so that unnecessary prompting doesn't happen if a package's
23
#     installation fails and the `postinst' is called with `abort-upgrade',
24
#     `abort-remove' or `abort-deconfigure'.
25

  
26
PACKAGE=larpe
27
VERSION=2.4
28
LIB="/usr/lib/python$VERSION"
29
DIRLIST="/usr/share/pycentral/larpe/site-packages/larpe/"
30

  
31
case "$1" in
32
    configure|abort-upgrade|abort-remove|abort-deconfigure)
33
        for i in $DIRLIST ; do
34
            /usr/bin/python$VERSION -O $LIB/compileall.py -q $i
35
            /usr/bin/python$VERSION $LIB/compileall.py -q $i
36
        done
37

  
38
        # Load Apache 2 modules
39
        for module in "proxy" "rewrite" "headers" "proxy_http"; do
40
            a2enmod ${module} > /dev/null || true
41
        done
42

  
43
        # Restart Apache 2
44
        set +e
45
        if [ -x /usr/sbin/invoke-rc.d ]; then
46
            invoke-rc.d apache2 restart || true
47
        else
48
            /etc/init.d/apache2 restart || true
49
        fi
50
        set -e
51
    ;;
52

  
53
    *)
54
        echo "postinst called with unknown argument \`$1'" >&2
55
        exit 1
56
    ;;
57
esac
58

  
59

  
60

  
61
# dh_installdeb will replace this with shell code automatically
62
# generated by other debhelper scripts.
63

  
64
#DEBHELPER#
65

  
66
exit 0
larpe/branches/idwsf/debian/prerm
1
#! /bin/sh
2
# prerm script for larpe
3
#
4
# see: dh_installdeb(1)
5

  
6
set -e
7

  
8
# summary of how this script can be called:
9
#        * <prerm> `remove'
10
#        * <old-prerm> `upgrade' <new-version>
11
#        * <new-prerm> `failed-upgrade' <old-version>
12
#        * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
13
#        * <deconfigured's-prerm> `deconfigure' `in-favour'
14
#          <package-being-installed> <version> `removing'
15
#          <conflicting-package> <version>
16
# for details, see http://www.debian.org/doc/debian-policy/ or
17
# the debian-policy package
18

  
19

  
20
PACKAGE=larpe
21

  
22
case "$1" in
23
    remove|upgrade|deconfigure)
24
    	dpkg --listfiles $PACKAGE |
25
                  awk '$0~/\.py$/ {print $0"c\n" $0"o"}' |
26
                  xargs rm -f >&2
27
        ;;
28
    failed-upgrade)
29
        ;;
30
    *)
31
        echo "prerm called with unknown argument \`$1'" >&2
32
        exit 1
33
    ;;
34
esac
35

  
36
# dh_installdeb will replace this with shell code automatically
37
# generated by other debhelper scripts.
38

  
39
#DEBHELPER#
40

  
41
exit 0
larpe/branches/idwsf/debian/pycompat
1
2
larpe/branches/idwsf/debian/rules
1
#!/usr/bin/make -f
2
# GNU copyright 1997 to 1999 by Joey Hess.
3

  
4
# Uncomment this to turn on verbose mode.
5
#export DH_VERBOSE=1
6

  
7
PYTHON=/usr/bin/python2.4
8

  
9
ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS)))
10
	CFLAGS += -g
11
endif
12
ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
13
	INSTALL_PROGRAM += -s
14
endif
15

  
16
build: build-stamp
17

  
18
build-stamp:
19
	dh_testdir
20
	touch build-stamp
21

  
22
clean:
23
	dh_testdir
24
	dh_testroot
25
	rm -f build-stamp
26

  
27
	make clean
28

  
29
	dh_clean
30

  
31
install: build
32
	dh_testdir
33
	dh_testroot
34
	dh_clean -k
35
	dh_installdirs
36

  
37
	make install prefix=/usr DESTDIR=$(CURDIR)/debian/larpe/
38
	dh_install apache2-vhost-larpe etc/apache2/sites-available
39
	find debian/larpe -name "*.pyc" -exec rm -f {} \;
40

  
41
# Build architecture-independent files here.
42
binary-indep: build install
43
# We have nothing to do by default.
44

  
45
# Build architecture-dependent files here.
46
binary-arch: build install
47
	dh_testdir
48
	dh_testroot
49
	dh_installdocs
50
	dh_installinit
51
	dh_installchangelogs
52
#	dh_installdebconf
53
	dh_link
54
	dh_strip
55
	dh_compress
56
	dh_fixperms -X /var/lib/larpe -X /usr/sbin/larpe-reload-apache2
57
	dh_pycentral
58
	dh_installdeb
59
	dh_gencontrol
60
	dh_md5sums
61
	dh_builddeb
62

  
63
binary: binary-indep binary-arch
64
.PHONY: build clean binary-indep binary-arch binary install
larpe/branches/idwsf/debian/templates
1
Template: larpe/hostname
2
Type: string
3
Default: localhost
4
Description: Hostname :
5
 This is the name by which this reverse-proxy will be known on the network.
6
 Your DNS server must have been configured accordingly.
7

  
8
Template: larpe/enable_vhost
9
Type: boolean
10
Default: false
11
Description: Enable this vhost :
12
 A new virtual host for Larpe will be created with the hostname you chose. It may break
13
 your Apache2 configuration.
14
 .
15
 If you didn't tweak your Apache2 configuration a lot
16
 and you don't have vital websites on the same server, you can safely say "yes" here
17
 to enable it, and fix it later if needed.
18
 .
19
 If you prefer checking this vhost will fit well with your Apache2 configuration first,
20
 and enable it by yourself later, say "no".
21

  
22
Template: larpe/admin_username
23
Type: string
24
Default: admin
25
Description: Administrator login :
26
 This is the login which will be used to connect to the administrator interface
27

  
28
Template: larpe/admin_password
29
Type: password
30
Description: Administrator password :
31
 This is the password which will be used to connect to the administrator interface
32

  
33
Template: larpe/admin_email
34
Type: string
35
Default: root@localhost
36
Description: Administrator email address :
37
 This is the email address to which problem reports will be sent
larpe/branches/idwsf/doc/Makefile
1
all:
2
	$(MAKE) -C en
3
#	$(MAKE) -C fr
4

  
5
clean:
6
	$(MAKE) -C en clean
7
#	$(MAKE) -C fr clean
8

  
9
.PHONY: clean
10

  
larpe/branches/idwsf/doc/en/Makefile
1
RST2HTML = rst2html
2
RST2LATEX = ../scripts/rst2latex.py
3
PDFLATEX = pdflatex
4
RM = rm -f
5

  
6
all: larpe-admin.pdf larpe-admin.html
7

  
8
%.html: %.rst
9
	$(RST2HTML) --stylesheet=default.css --link-stylesheet --language=en $? > $@
10

  
11
figures-no-alpha-stamp:
12
	-$(RM) -r figures-no-alpha/
13
	mkdir figures-no-alpha/
14
	for F in figures/*.png; do \
15
		../scripts/removealpha.sh $$F figures-no-alpha/`basename $$F`; \
16
	done
17
	touch figures-no-alpha-stamp
18

  
19
%.tex: %.rst #figures-no-alpha-stamp
20
	cat $? | sed -e 's/figures\//figures-no-alpha\//' \
21
			-e 's/ ::$$/ : ::/g' \
22
			-e 's/.. section-numbering:://' | $(RST2LATEX) --language=en > $@
23

  
24
%.pdf: %.tex custom.tex
25
	$(PDFLATEX) $?
26
	logfile=`echo "$@" |sed -r "s/(.*)....$$/\\1/"`.log; while [ -f "$$logfile" -a -n "`grep "Rerun to get cross-references right" $$logfile`" ]; do $(PDFLATEX) $< ; done
27

  
28
clean:
29
	-$(RM) *.aux *.toc *.log *.out
30
	-$(RM) larpe-admin.pdf
31
	-$(RM) larpe-admin.tex
32
	-$(RM) larpe-admin.html
33
	-$(RM) -r figures-no-alpha figures-no-alpha-stamp
34

  
35
.PHONY: all clean
larpe/branches/idwsf/doc/en/custom.tex
1
\usepackage{float,fancyhdr,lscape,sectsty,colortbl,color,lastpage,setspace}
2
\usepackage[perpage,bottom]{footmisc}
3
\usepackage[hang]{caption2}
4
\usepackage{marvosym}
5

  
6
\usepackage{float,url,listings,tocbibind,fancyhdr,calc,placeins}
7

  
8
\usepackage{palatino}
9
\usepackage[Glenn]{fncychap}
10

  
11
\pagestyle{fancy}
12
\fancyhead{}
13
\fancyfoot{}
14
\fancyhead[L]{Authentic}
15
\fancyhead[R]{Administrator Guide}
16
\fancyfoot[C]{Page \thepage}
17
\addtolength{\headheight}{1.6pt}
18

  
19
\setlength\parindent{0pt}
20
\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
21
\setlength\abovecaptionskip{0.1ex}
22

  
23
\makeatletter
24
\renewcommand{\maketitle}{\begin{titlepage}%
25
    \let\footnotesize\small
26
    \let\footnoterule\relax
27
    \parindent \z@
28
    \reset@font
29
    \null\vfil
30
    \begin{flushleft}
31
      \huge \@title
32
    \end{flushleft}
33
    \par
34
    \hrule height 1pt
35
    \par
36
    \begin{flushright}
37
      \LARGE \@author \par
38
    \end{flushright}
39
    \vskip 60\p@
40
    \vfil\null
41
  \end{titlepage}%
42
  \setcounter{footnote}{0}%
43
}
44
\makeatother
45

  
larpe/branches/idwsf/doc/en/default.css
1
body {
2
	font-family: sans-serif;
3
}
4

  
5

  
6
h1 a, h2 a, h3 a, h4 a {
7
	text-decoration: inherit;
8
	color: inherit;
9
}
10

  
11
pre.literal-block {
12
	background: #eee;
13
	border: 1px inset black;
14
	padding: 2px;
15
	margin: auto 10px;
16
	overflow: auto;
17
}
18

  
19
h1.title {
20
	text-align: center;
21
	background: #eef;
22
	border: 1px solid #aaf;
23
	letter-spacing: 1px;
24
}
25

  
26
div.section {
27
	margin-bottom: 2em;
28
}
29

  
30
div.section h1 {
31
	padding: 0 15px;
32
	background: #eef;
33
	border: 1px solid #aaf;
34
}
35

  
36
div.section h2 {
37
	padding: 0 15px;
38
	background: #eef;
39
	border: 1px solid #aaf;
40
}
41

  
42
div.document {
43
	margin-top: 1em;
44
	border-top: 1px solid #aaf;
45
	border-bottom: 1px solid #aaf;
46
}
47

  
48
div.section p,
49
div.section ul {
50
	text-align: justify;
51
}
52

  
53
div.contents {
54
	float: right;
55
	border: 1px solid black;
56
	margin: 1em;
57
	background: #eef;
58
	max-width: 33%;
59
}
60

  
61
div#building-liberty-services-with-lasso div#table-of-contents {
62
	max-width: inherit;
63
	float: none;
64
	background: white url(lasso.png) bottom right no-repeat;
65
}
66

  
67
div.contents ul {
68
	padding-left: 1em;
69
	list-style: none;
70
}
71

  
72
div.contents li {
73
	padding-bottom: 2px;
74
}
75

  
76
div.contents p {
77
	background: #ddf;
78
	text-align: center;
79
	border-bottom: 1px solid black;
80
	margin: 0;
81
}
82

  
83
th.docinfo-name {
84
	text-align: right;
85
	padding-right: 0.5em;
86
}
87

  
88
dd {
89
	margin-bottom: 1ex;
90
}
91

  
92
table.table {
93
	margin: 1ex 0;
94
	border-spacing: 0px;
95
}
96

  
97

  
98
table.table th {
99
	padding: 0px 1ex;
100
	background: #eef;
101
	font-weight: normal;
102
}
103

  
104

  
105
table.table td {
106
	padding: 0 0.5ex;
107
}
108

  
109
div.note, div.warning {
110
	padding: 0.3ex;
111
	padding-left: 60px;
112
	min-height: 50px;
113
	margin: 1ex 1em;
114
}
115

  
116
div.note {
117
	background: #ffa url(note.png) top left no-repeat;
118
	border: 1px solid #fd8;
119
}
120

  
121
div.warning {
122
	background: #ffd url(warning.png) top left no-repeat;
123
}
124

  
125
p.admonition-title {
126
	font-weight: bold;
127
	display: inline;
128
	display: none;
129
	padding-right: 1em;
130
}
131

  
132
div.figure {
133
	margin: 0 auto;
134
	width: 70%;
135
	min-width: 800px;
136
	text-align: center;
137
}
138

  
139
div.figure p.caption {
140
	font-style: italic;
141
	margin: 1ex 0 2em 0;
142
	text-align: center;
143
}
larpe/branches/idwsf/doc/en/fncychap.sty
1
%%% Copyright   Ulf A. Lindgren
2
%%%
3
%%% Note        Premission is granted to modify this file under
4
%%%             the condition that it is saved using another
5
%%%             file and package name.
6
%%%
7
%%% Revision    1.1 (1997)
8
%%%
9
%%%             Jan. 8th Modified package name base date option
10
%%%             Jan. 22th Modified FmN and FmTi for error in book.cls
11
%%%                  \MakeUppercase{#}->{\MakeUppercase#}
12
%%%             Apr. 6th Modified Lenny option to prevent undesired 
13
%%%                  skip of line.
14
%%%             Nov. 8th Fixed \@chapapp for AMS
15
%%%
16
%%% Revision    1.2 (1998)
17
%%%
18
%%%             Feb. 11th Fixed appendix problem related to Bjarne
19
%%%             Aug. 11th Fixed problem related to 11pt and 12pt 
20
%%%                  suggested by Tomas Lundberg. THANKS!
21
%%%
22
%%% Revision    1.3 (2004)
23
%%%             Sep. 20th problem with frontmatter, mainmatter and
24
%%%                  backmatter, pointed out by Lapo Mori
25
%%%
26
%%% Revision    1.31 (2004)
27
%%%             Sep. 21th problem with the Rejne definition streched text
28
%%%                  caused ugly gaps in the vrule aligned with the title
29
%%%                  text. Kindly pointed out to me by Hendri Adriaens
30
%%%
31
%%% Revision    1.32 (2005)
32
%%%             Jun. 23th compatibility problem with the KOMA class 'scrbook.cls'
33
%%%                  a remedy is a redefinition of '\@schapter' in
34
%%%                  line with that used in KOMA. The problem was pointed
35
%%%                  out to me by Mikkel Holm Olsen
36
%%%
37
%%% Revision    1.33 (2005)
38
%%%             Aug. 9th misspelled ``TWELV'' corrected, the error was pointed
39
%%%                  out to me by George Pearson
40
%%%
41

  
42

  
43
%%% Last modified   Aug. 9th 2005
44

  
45
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
46
\ProvidesPackage{fncychap}
47
             [2004/09/21 v1.33
48
                 LaTeX package (Revised chapters)]
49

  
50
%%%% DEFINITION OF Chapapp variables
51
\newcommand{\CNV}{\huge\bfseries}
52
\newcommand{\ChNameVar}[1]{\renewcommand{\CNV}{#1}}
53

  
54

  
55
%%%% DEFINITION OF TheChapter variables
56
\newcommand{\CNoV}{\huge\bfseries}
57
\newcommand{\ChNumVar}[1]{\renewcommand{\CNoV}{#1}}
58

  
59
\newif\ifUCN
60
\UCNfalse
61
\newif\ifLCN
62
\LCNfalse
63
\def\ChNameLowerCase{\LCNtrue\UCNfalse}
64
\def\ChNameUpperCase{\UCNtrue\LCNfalse}
65
\def\ChNameAsIs{\UCNfalse\LCNfalse}
66

  
67
%%%%% Fix for AMSBook 971008
68

  
69
\@ifundefined{@chapapp}{\let\@chapapp\chaptername}{}
70

  
71

  
72
%%%%% Fix for Bjarne and appendix 980211
73

  
74
\newif\ifinapp
75
\inappfalse
76
\renewcommand\appendix{\par
77
  \setcounter{chapter}{0}%
78
  \setcounter{section}{0}%
79
  \inapptrue%
80
  \renewcommand\@chapapp{\appendixname}%
81
  \renewcommand\thechapter{\@Alph\c@chapter}}
82

  
83
%%%%% Fix for frontmatter, mainmatter, and backmatter 040920
84

  
85
\@ifundefined{@mainmatter}{\newif\if@mainmatter \@mainmattertrue}{}
86

  
87
%%%%%
88

  
89

  
90

  
91
\newcommand{\FmN}[1]{%
92
\ifUCN
93
   {\MakeUppercase#1}\LCNfalse
94
\else
95
   \ifLCN
96
      {\MakeLowercase#1}\UCNfalse
97
   \else #1
98
   \fi
99
\fi}
100

  
101

  
102
%%%% DEFINITION OF Title variables
103
\newcommand{\CTV}{\Huge\bfseries}
104
\newcommand{\ChTitleVar}[1]{\renewcommand{\CTV}{#1}}
105

  
106
%%%% DEFINITION OF the basic rule width
107
\newlength{\RW}
108
\setlength{\RW}{1pt}
109
\newcommand{\ChRuleWidth}[1]{\setlength{\RW}{#1}}
110

  
111
\newif\ifUCT
112
\UCTfalse
113
\newif\ifLCT
114
\LCTfalse
115
\def\ChTitleLowerCase{\LCTtrue\UCTfalse}
116
\def\ChTitleUpperCase{\UCTtrue\LCTfalse}
117
\def\ChTitleAsIs{\UCTfalse\LCTfalse}
118
\newcommand{\FmTi}[1]{%
119
\ifUCT
120
   {\MakeUppercase#1}\LCTfalse
121
\else
122
   \ifLCT
123
      {\MakeLowercase#1}\UCTfalse
124
   \else {#1}
125
   \fi
126
\fi}
127

  
128

  
129

  
130
\newlength{\mylen}
131
\newlength{\myhi}
132
\newlength{\px}
133
\newlength{\py}
134
\newlength{\pyy}
135
\newlength{\pxx}
136

  
137

  
138
\def\mghrulefill#1{\leavevmode\leaders\hrule\@height #1\hfill\kern\z@}
139

  
140
\newcommand{\DOCH}{%
141
  \CNV\FmN{\@chapapp}\space \CNoV\thechapter
142
  \par\nobreak
143
  \vskip 20\p@
144
  }
145
\newcommand{\DOTI}[1]{%
146
    \CTV\FmTi{#1}\par\nobreak
147
    \vskip 40\p@
148
    }
149
\newcommand{\DOTIS}[1]{%
150
    \CTV\FmTi{#1}\par\nobreak
151
    \vskip 40\p@
152
    }
153

  
154
%%%%%% SONNY DEF
155

  
156
\DeclareOption{Sonny}{%
157
  \ChNameVar{\Large\sf}
158
  \ChNumVar{\Huge}
159
  \ChTitleVar{\Large\sf}
160
  \ChRuleWidth{0.5pt}
161
  \ChNameUpperCase
162
  \renewcommand{\DOCH}{%
163
    \raggedleft
164
    \CNV\FmN{\@chapapp}\space \CNoV\thechapter
165
    \par\nobreak
166
    \vskip 40\p@}
167
  \renewcommand{\DOTI}[1]{%
168
    \CTV\raggedleft\mghrulefill{\RW}\par\nobreak
169
    \vskip 5\p@
170
    \CTV\FmTi{#1}\par\nobreak
171
    \mghrulefill{\RW}\par\nobreak
172
    \vskip 40\p@}
173
  \renewcommand{\DOTIS}[1]{%
174
    \CTV\raggedleft\mghrulefill{\RW}\par\nobreak
175
    \vskip 5\p@
176
    \CTV\FmTi{#1}\par\nobreak
177
    \mghrulefill{\RW}\par\nobreak
178
    \vskip 40\p@}
179
}
180

  
181
%%%%%% LENNY DEF
182

  
183
\DeclareOption{Lenny}{%
184

  
185
  \ChNameVar{\fontsize{14}{16}\usefont{OT1}{phv}{m}{n}\selectfont}
186
  \ChNumVar{\fontsize{60}{62}\usefont{OT1}{ptm}{m}{n}\selectfont}
187
  \ChTitleVar{\Huge\bfseries\rm}
188
  \ChRuleWidth{1pt}
189
  \renewcommand{\DOCH}{%
190
    \settowidth{\px}{\CNV\FmN{\@chapapp}}
191
    \addtolength{\px}{2pt}
192
    \settoheight{\py}{\CNV\FmN{\@chapapp}}
193
    \addtolength{\py}{1pt}
194

  
195
    \settowidth{\mylen}{\CNV\FmN{\@chapapp}\space\CNoV\thechapter}
196
    \addtolength{\mylen}{1pt}
197
    \settowidth{\pxx}{\CNoV\thechapter}
198
    \addtolength{\pxx}{-1pt}
199

  
200
    \settoheight{\pyy}{\CNoV\thechapter}
201
    \addtolength{\pyy}{-2pt}
202
    \setlength{\myhi}{\pyy}
203
    \addtolength{\myhi}{-1\py}
204
    \par
205
    \parbox[b]{\textwidth}{%
206
    \rule[\py]{\RW}{\myhi}%
207
    \hskip -\RW%
208
    \rule[\pyy]{\px}{\RW}%
209
    \hskip -\px%
210
    \raggedright%
211
    \CNV\FmN{\@chapapp}\space\CNoV\thechapter%
212
    \hskip1pt%
213
    \mghrulefill{\RW}%
214
    \rule{\RW}{\pyy}\par\nobreak%
215
    \vskip -\baselineskip%
216
    \vskip -\pyy%
217
    \hskip \mylen%
218
    \mghrulefill{\RW}\par\nobreak%
219
    \vskip \pyy}%
220
    \vskip 20\p@}
221
 
222

  
223
  \renewcommand{\DOTI}[1]{%
224
    \raggedright
225
    \CTV\FmTi{#1}\par\nobreak
226
    \vskip 40\p@}
227

  
228
  \renewcommand{\DOTIS}[1]{%
229
    \raggedright
230
    \CTV\FmTi{#1}\par\nobreak
231
    \vskip 40\p@}
232
 }
233

  
234

  
235
%%%%%%% GLENN DEF
236

  
237

  
238
\DeclareOption{Glenn}{%
239
  \ChNameVar{\bfseries\Large\sf}
240
  \ChNumVar{\Huge}
241
  \ChTitleVar{\bfseries\Large\rm}
242
  \ChRuleWidth{1pt}
243
  \ChNameUpperCase
244
  \ChTitleUpperCase
245
  \renewcommand{\DOCH}{%
246
    \settoheight{\myhi}{\CTV\FmTi{Test}}
247
    \setlength{\py}{\baselineskip}
248
    \addtolength{\py}{\RW}
249
    \addtolength{\py}{\myhi}
250
    \setlength{\pyy}{\py}
251
    \addtolength{\pyy}{-1\RW}
252
     
253
    \raggedright
254
    \CNV\FmN{\@chapapp}\space\CNoV\thechapter
255
    \hskip 3pt\mghrulefill{\RW}\rule[-1\pyy]{2\RW}{\py}\par\nobreak}
256

  
257
  \renewcommand{\DOTI}[1]{%
258
    \addtolength{\pyy}{-4pt}
259
    \settoheight{\myhi}{\CTV\FmTi{#1}}
260
    \addtolength{\myhi}{\py}
261
    \addtolength{\myhi}{-1\RW}
262
    \vskip -1\pyy
263
    \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 2pt
264
    \raggedleft\CTV\FmTi{#1}\par\nobreak
265
    \vskip 80\p@}
266

  
267
\newlength{\backskip}
268
  \renewcommand{\DOTIS}[1]{%
269
%    \setlength{\py}{10pt}
270
%    \setlength{\pyy}{\py}
271
%    \addtolength{\pyy}{\RW}
272
%    \setlength{\myhi}{\baselineskip}
273
%    \addtolength{\myhi}{\pyy}
274
%    \mghrulefill{\RW}\rule[-1\py]{2\RW}{\pyy}\par\nobreak
275
%    \addtolength{}{}
276
%\vskip -1\baselineskip
277
%    \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 2pt
278
%    \raggedleft\CTV\FmTi{#1}\par\nobreak
279
%    \vskip 60\p@}
280
%% Fix suggested by Tomas Lundberg
281
    \setlength{\py}{25pt}  % eller vad man vill
282
    \setlength{\pyy}{\py}
283
    \setlength{\backskip}{\py}
284
    \addtolength{\backskip}{2pt}
285
    \addtolength{\pyy}{\RW}
286
    \setlength{\myhi}{\baselineskip}
287
    \addtolength{\myhi}{\pyy}
288
    \mghrulefill{\RW}\rule[-1\py]{2\RW}{\pyy}\par\nobreak
289
    \vskip -1\backskip
290
    \rule{2\RW}{\myhi}\mghrulefill{\RW}\hskip 3pt %
291
    \raggedleft\CTV\FmTi{#1}\par\nobreak
292
    \vskip 40\p@}
293
 }
294

  
295
%%%%%%% CONNY DEF
296

  
297
\DeclareOption{Conny}{%
298
  \ChNameUpperCase
299
  \ChTitleUpperCase  
300
  \ChNameVar{\centering\Huge\rm\bfseries}
301
  \ChNumVar{\Huge}
302
  \ChTitleVar{\centering\Huge\rm}
303
  \ChRuleWidth{2pt}
304

  
305
  \renewcommand{\DOCH}{%
306
    \mghrulefill{3\RW}\par\nobreak
307
    \vskip -0.5\baselineskip
308
    \mghrulefill{\RW}\par\nobreak
309
    \CNV\FmN{\@chapapp}\space \CNoV\thechapter
310
    \par\nobreak
311
    \vskip -0.5\baselineskip
312
   }
313
  \renewcommand{\DOTI}[1]{%
314
    \mghrulefill{\RW}\par\nobreak
315
    \CTV\FmTi{#1}\par\nobreak
316
    \vskip 60\p@
317
    }
318
  \renewcommand{\DOTIS}[1]{%
319
    \mghrulefill{\RW}\par\nobreak
320
    \CTV\FmTi{#1}\par\nobreak
321
    \vskip 60\p@
322
    }
323
  }
324

  
325
%%%%%%% REJNE DEF
326

  
327
\DeclareOption{Rejne}{%
328

  
329
  \ChNameUpperCase
330
  \ChTitleUpperCase  
331
  \ChNameVar{\centering\Large\rm}
332
  \ChNumVar{\Huge}
333
  \ChTitleVar{\centering\Huge\rm}
334
  \ChRuleWidth{1pt}
335
  \renewcommand{\DOCH}{%
336
    \settoheight{\py}{\CNoV\thechapter}
337
    \parskip=0pt plus 1pt % Set parskip to default, just in case v1.31
338
    \addtolength{\py}{-1pt}
339
    \CNV\FmN{\@chapapp}\par\nobreak
340
    \vskip 20\p@
341
    \setlength{\myhi}{2\baselineskip}
342
    \setlength{\px}{\myhi}
343
    \addtolength{\px}{-1\RW}
344
    \rule[-1\px]{\RW}{\myhi}\mghrulefill{\RW}\hskip
345
    10pt\raisebox{-0.5\py}{\CNoV\thechapter}\hskip 10pt\mghrulefill{\RW}\rule[-1\px]{\RW}{\myhi}\par\nobreak
346
     \vskip -3\p@% Added -2pt vskip to correct for streched text v1.31
347
    }
348
  \renewcommand{\DOTI}[1]{%
349
    \setlength{\mylen}{\textwidth}
350
    \parskip=0pt plus 1pt % Set parskip to default, just in case v1.31
351
    \addtolength{\mylen}{-2\RW}
352
    {\vrule width\RW}\parbox{\mylen}{\CTV\FmTi{#1}}{\vrule width\RW}\par\nobreak%
353
    \vskip -3pt\rule{\RW}{2\baselineskip}\mghrulefill{\RW}\rule{\RW}{2\baselineskip}%
354
    \vskip 60\p@% Added -2pt in vskip to correct for streched text v1.31
355
    }
356
  \renewcommand{\DOTIS}[1]{%
357
    \setlength{\py}{\fboxrule}
358
    \setlength{\fboxrule}{\RW}
359
    \setlength{\mylen}{\textwidth}
360
    \addtolength{\mylen}{-2\RW}
361
    \fbox{\parbox{\mylen}{\vskip 2\baselineskip\CTV\FmTi{#1}\par\nobreak\vskip \baselineskip}} 
362
    \setlength{\fboxrule}{\py}
363
    \vskip 60\p@
364
    }
365
  }
366

  
367

  
368
%%%%%%% BJARNE DEF
369

  
370
\DeclareOption{Bjarne}{%
371
  \ChNameUpperCase
372
  \ChTitleUpperCase  
373
  \ChNameVar{\raggedleft\normalsize\rm}
374
  \ChNumVar{\raggedleft \bfseries\Large}
375
  \ChTitleVar{\raggedleft \Large\rm}
376
  \ChRuleWidth{1pt}
377

  
378

  
379
%% Note thechapter -> c@chapter fix appendix bug
380
%% Fixed misspelled 12
381

  
382
  \newcounter{AlphaCnt}
383
  \newcounter{AlphaDecCnt}
384
  \newcommand{\AlphaNo}{%
385
    \ifcase\number\theAlphaCnt
386
      \ifnum\c@chapter=0
387
        ZERO\else{}\fi
388
    \or ONE\or TWO\or THREE\or FOUR\or FIVE
389
    \or SIX\or SEVEN\or EIGHT\or NINE\or TEN
390
    \or ELEVEN\or TWELVE\or THIRTEEN\or FOURTEEN\or FIFTEEN
391
    \or SIXTEEN\or SEVENTEEN\or EIGHTEEN\or NINETEEN\fi
392
}
393

  
394
  \newcommand{\AlphaDecNo}{%
395
    \setcounter{AlphaDecCnt}{0}
396
    \@whilenum\number\theAlphaCnt>0\do
397
      {\addtocounter{AlphaCnt}{-10}
398
       \addtocounter{AlphaDecCnt}{1}}
399
     \ifnum\number\theAlphaCnt=0
400
     \else
401
       \addtocounter{AlphaDecCnt}{-1}
402
       \addtocounter{AlphaCnt}{10}
403
     \fi
404
     
405
     
406
    \ifcase\number\theAlphaDecCnt\or TEN\or TWENTY\or THIRTY\or
407
    FORTY\or FIFTY\or SIXTY\or SEVENTY\or EIGHTY\or NINETY\fi
408
    }
409
  \newcommand{\TheAlphaChapter}{%
410
    
411
    \ifinapp 
412
      \thechapter
413
    \else
414
      \setcounter{AlphaCnt}{\c@chapter}
415
      \ifnum\c@chapter<20
416
        \AlphaNo
417
      \else
418
        \AlphaDecNo\AlphaNo
419
      \fi
420
    \fi
421
    }  
422
  \renewcommand{\DOCH}{%
423
    \mghrulefill{\RW}\par\nobreak
424
    \CNV\FmN{\@chapapp}\par\nobreak 
425
    \CNoV\TheAlphaChapter\par\nobreak
426
    \vskip -1\baselineskip\vskip 5pt\mghrulefill{\RW}\par\nobreak
427
    \vskip 20\p@
428
    }
429
  \renewcommand{\DOTI}[1]{%
430
    \CTV\FmTi{#1}\par\nobreak
431
    \vskip 40\p@
432
    }
433
  \renewcommand{\DOTIS}[1]{%
434
    \CTV\FmTi{#1}\par\nobreak
435
    \vskip 40\p@
436
    }
437
}
438

  
439
\DeclareOption*{%
440
  \PackageWarning{fancychapter}{unknown style option}
441
  }
442

  
443
\ProcessOptions* \relax
444

  
445
\def\@makechapterhead#1{%
446
  \vspace*{50\p@}%
447
  {\parindent \z@ \raggedright \normalfont
448
    \ifnum \c@secnumdepth >\m@ne
449
      \if@mainmatter%%%%% Fix for frontmatter, mainmatter, and backmatter 040920
450
        \DOCH
451
      \fi
452
    \fi
453
    \interlinepenalty\@M
454
    \DOTI{#1}
455
  }}
456

  
457

  
458
%%% Begin: To avoid problem with scrbook.cls (fncychap version 1.32)
459

  
460
%%OUT:
461
%\def\@schapter#1{\if@twocolumn
462
%                   \@topnewpage[\@makeschapterhead{#1}]%
463
%                 \else
464
%                   \@makeschapterhead{#1}%
465
%                   \@afterheading
466
%                 \fi}
467

  
468
%%IN:
469
\def\@schapter#1{%
470
\if@twocolumn%
471
  \@makeschapterhead{#1}%
472
\else%
473
  \@makeschapterhead{#1}%
474
  \@afterheading%
475
\fi}
476

  
477
%%% End: To avoid problem with scrbook.cls (fncychap version 1.32)
478

  
479
\def\@makeschapterhead#1{%
480
  \vspace*{50\p@}%
481
  {\parindent \z@ \raggedright
482
    \normalfont
483
    \interlinepenalty\@M
484
    \DOTIS{#1}
485
    \vskip 40\p@
486
  }}
487

  
488
\endinput
489

  
490

  
larpe/branches/idwsf/doc/en/larpe-admin.rst
1
=====================================
2
Larpe - Administrator Guide
3
=====================================
4

  
5
:author: Damien Laniel
6
:contact: dlaniel@entrouvert.com
7
:copyright: Copyright © 2006 Entr'ouvert
8

  
9
.. contents:: Table of contents
10

  
11
Overview
12
========
13

  
14
Larpe is a Liberty Alliance Reverse Proxy. It allows any service provider
15
(that is a website) to use Liberty Alliance features (Identity federation,
16
Single Sign On and Single Logout) without changing the code of
17
the service provider itself. It uses the Lasso_ library
18
which is certified by the `Liberty Alliance`_ consortium. Lasso_ and Larpe
19
are released under the terms of the `GNU GPL license`_.
20

  
21

  
22
How to get and install Larpe
23
============================
24

  
25
Installation under Debian_ Sarge
26
++++++++++++++++++++++++++++++++
27

  
28
To work correctly Larpe relies on :
29

  
30
* Apache2_ ;
31

  
32
* Lasso_ (0.6.3) ;
33

  
34
* Quixote_ (2.0) ;
35

  
36
* SCGI_ ;
37

  
38
* mod_python_ ;
39

  
40
* libxml2 ;
41

  
42
* mod_proxy_html.
43

  
44
You will also need a Liberty Alliance Identity Provider, be it on the same server or not.
45
We recommend Authentic_ for that need.
46

  
47
Package Installation
48
--------------------
49

  
50
You need to add the following line to your /etc/apt/sources.list; this will
51
give you access to the repository where Larpe is stored::
52

  
53
 deb http://deb.entrouvert.org/ sarge main
54

  
55
As root type::
56

  
57
 apt-get update
58
 apt-get install larpe
59

  
60
And follow the debconf wizard to set it up.
61

  
62
All the required packages are now installed and configured.
63

  
64
You might need to change the "<VirtualHost \*>" in your apache2 configuration
65
(/etc/apache2/sites-available/apache2-vhost-larpe) depending on how you
66
previously configured apache.
67

  
68
Don't forget to modify your /etc/hosts file if necessary. Larpe now works, the
69
administration interface is reachable at http://your_domain_name/admin. The username
70
and password are the ones you entered during the installation wizard.
71

  
72
If you don't want to modify your sources.list file, you can manually dowload and
73
install the required packages with the dpkg -i command :
74

  
75
* Larpe, Authentic and Lasso on http://deb.entrouvert.org/ ;
76

  
77
* Quixote 2.0 on http://authentic.labs.libre-entreprise.org/.
78

  
79
Installation with another Linux distribution
80
++++++++++++++++++++++++++++++++++++++++++++
81

  
82
We suppose Apache2_, SCGI_, mod_python_, libxml2 and mod_proxy_html are already installed. You need then to
83
download and install the following sources :
84

  
85
* Lasso http://lasso.entrouvert.org ;
86

  
87
* Quixote http://www.mems-exchange.org/software/Quixote/ ;
88

  
89
* Authentic http://authentic.labs.libre-entreprise.org/ ;
90

  
91
* Larpe http://labs.libre-entreprise.org/frs/?group_id=108.
92

  
93
To install Larpe, uncompress the sources you have downloaded and launch the
94
setup.py script ::
95

  
96
 tar xzf larpe*.tar.gz
97
 cd larpe*
98
 python setup.py install
99

  
100
You need then to configure Apache2_ correctly. You should use the provided apache2-vhost-larpe template and adapt to your configuration.
101

  
102
Don't forget to modify your /etc/hosts file if necessary. Larpe now works, the
103
administration interface is reachable at http://your_domain_name/admin.
104

  
105
Basic Larpe configuration
106
=========================
107

  
108
Identity Provider configuration
109
+++++++++++++++++++++++++++++++
110

  
111
If you don't have a configured Identity Provider yet, please read Authentic
112
manual to set it up. Then you must have the metadata and public key of the Identity
113
Provider to begin with Larpe.
114

  
115
Then in Larpe administration interface, click on "Settings", then "Identity Provider".
116
Fill in the metadata and public key that you've got from your Identity Provider then
117
click Submit.
118
Your Identity Provider is now configured in Larpe, you can then configure as many Service
119
Providers as you want.
120

  
121
Service Provider Configuration
122
++++++++++++++++++++++++++++++
123

  
124
Service Provider configuration
125
------------------------------
126

  
127
Click on "Hosts" then "New Host".
128

  
129
Fill in the following parameters :
130

  
131
* Label : the name you want to give to your Service Provider ;
132

  
133
* Original Site Address : the root URL of your Service Provider ;
134

  
135
* Authentication Page : if the page which contains the authentication form for
136
  your Service Provider is on a separate page, fill the url of this page here ;
137

  
138
* Authentication Form Page : if you didn't fill the previous field and if the
139
  authentication form if not on the first page of your Service Provider either,
140
  fill the url of the page which contains the authentication form here ;
141

  
142
* Logout Address : when you want Single Sign On and Identity Federation, you probably
143
  want Single Logout too. If so, fill the logout url of your original site here ;
144

  
145
* Reversed Host Name : the domain name where you want to access your Service Provider
146
  through the reverse proxy. It can be the domain name of Larpe or not ;
147

  
148
Then click "Submit". Wait a few seconds then go to http://reversed_host_name/reverse_directory/
149
to check if it works. If not, wait a bit more and try again. If it really doesn't work,
150
please submit a bug report at http://labs.libre-entreprise.org/tracker/?func=add&group_id=108&atid=512
151

  
152
Service Provider Example: Linuxfr
153
---------------------------------
154

  
155
To help you setup your own Service Provider, we provide an example of a working Service Provider
156
to guide you.
157

  
158
To setup Linuxfr, fill in the following parameters :
159

  
160
* Label : Linuxfr ;
161

  
162
* Original Site Address : http://linuxfr.org/ ;
163

  
164
* Authentication Page : Nothing here ;
165

  
166
* Authentication Form Page : http://linuxfr.org/pub/ ;
167

  
168
* Logout Address : http://linuxfr.org/close_session.html ;
169

  
170
* Reversed Host Name : linuxfr.reverse-proxy.example.com.
171

  
172
With "reverse-proxy.example.com" being the hostname you've set up before for your reverse-proxy
173

  
174
Don't forget to add this new hostname to your /etc/hosts as well.
175

  
176
You can then go to the reversed Linuxfr at http://linuxfr.reverse-proxy.example.com/
177

  
178
Service Provider Liberty Alliance final setup
179
---------------------------------------------
180

  
181
Now that you can access your Service Provider, you need a final step to use Liberty Alliance
182
features. Click on "Hosts", the click on the "Edit" icon of the Service Provider you've
183
just configured. Save the Service Provider Metadata (for ID-FF 1.2) and the Public Key
184
(right click then "Save as"). Configure this Service Provider on your Identity Provider
185
with these two files.
186

  
187
Licenses
188
========
189

  
190
Larpe, Authentic_, Candle_ and Lasso_ are released under the terms of the
191
`GNU GPL license`_.
192

  
193
.. _Lasso: http://lasso.entrouvert.org/
194
.. _`Liberty Alliance`: http://projectliberty.org/
195
.. _`GNU GPL License`: http://www.gnu.org/copyleft/gpl.html
196
.. _Debian: http://www.debian.org/
197
.. _Apache2: http://httpd.apache.org/
198
.. _Quixote: http://www.mems-exchange.org/software/Quixote
199
.. _mod_python: http://www.modpython.org/
200
.. _SCGI: http://www.mems-exchange.org/software/scgi/
201
.. _Candle: http://candle.labs.libre-entreprise.org/
202
.. _Authentic: http://www.entrouvert.com/fr/authentic/
larpe/branches/idwsf/doc/scripts/removealpha.sh
1
#! /bin/sh
2

  
3
size=$(identify $1 | cut -d ' ' -f 3)
4
composite $1 -size $(identify $1 | cut -d ' ' -f3) xc:white $2
5

  
larpe/branches/idwsf/doc/scripts/rst2latex.py
1
#! /usr/bin/python
2

  
3
"""A minimal reST frontend, to create appropriate LaTeX files."""
4

  
5
try:
6
    import locale
7
    locale.setlocale(locale.LC_ALL, '')
8
except:
9
    pass
10

  
11
from docutils.core import publish_cmdline, Publisher
12

  
13
def set_io(self, source_path=None, destination_path=None):
14
    Publisher.set_io_orig(self, source_path, destination_path='/dev/null')
15

  
16
Publisher.set_io_orig, Publisher.set_io = Publisher.set_io, set_io
17

  
18
output = publish_cmdline(writer_name='latex',
19
    settings_overrides = {
20
        'documentclass': 'report',
21
        'documentoptions': '11pt,a4paper,titlepage',
22
        'use_latex_toc': True,
23
        'use_latex_docinfo': True,
24
        'stylesheet': 'custom.tex'})
25

  
26
output = output.replace('\\includegraphics',
27
    '\\includegraphics[width=.9\\textwidth,height=15cm,clip,keepaspectratio]')
28
output = output.replace('\\begin{figure}[htbp]', '\\begin{figure}[H]')
29
print output
larpe/branches/idwsf/larpe-reload-apache2-script
1
#!/bin/sh
2

  
3
/etc/init.d/apache2 reload
larpe/branches/idwsf/larpe-reload-apache2.c
1
/*
2
  Template for a setuid program that calls a script.
3

  
4
  The script should be in an unwritable directory and should itself
5
  be unwritable.  In fact all parent directories up to the root
6
  should be unwritable.  The script must not be setuid, that's what
7
  this program is for.
8

  
9
  This is a template program.  You need to fill in the name of the
10
  script that must be executed.  This is done by changing the
11
  definition of FULL_PATH below.
12

  
13
  There are also some rules that should be adhered to when writing
14
  the script itself.
15

  
16
  The first and most important rule is to never, ever trust that the
17
  user of the program will behave properly.  Program defensively.
18
  Check your arguments for reasonableness.  If the user is allowed to
19
  create files, check the names of the files.  If the program depends
20
  on argv[0] for the action it should perform, check it.
21

  
22
  Assuming the script is a Bourne shell script, the first line of the
23
  script should be
24
  #!/bin/sh -
25
  The - is important, don't omit it.  If you're using esh, the first
26
  line should be
27
  #!/usr/local/bin/esh -f
28
  and for ksh, the first line should be
29
  #!/usr/local/bin/ksh -p
30
  The script should then set the variable IFS to the string
31
  consisting of <space>, <tab>, and <newline>.  After this (*not*
32
  before!), the PATH variable should be set to a reasonable value and
33
  exported.  Do not expect the PATH to have a reasonable value, so do
34
  not trust the old value of PATH.  You should then set the umask of
35
  the program by calling
36
  umask 077 # or 022 if you want the files to be readable
37
  If you plan to change directories, you should either unset CDPATH
38
  or set it to a good value.  Setting CDPATH to just ``.'' (dot) is a
39
  good idea.
40
  If, for some reason, you want to use csh, the first line should be
41
  #!/bin/csh -fb
42
  You should then set the path variable to something reasonable,
43
  without trusting the inherited path.  Here too, you should set the
44
  umask using the command
45
  umask 077 # or 022 if you want the files to be readable
46
*/
47

  
48
#include <unistd.h>
49
#include <stdlib.h>
50
#include <stdio.h>
51
#include <sys/types.h>
52
#include <sys/stat.h>
53
#include <string.h>
54

  
55
/* CONFIGURATION SECTION */
56

  
57
#ifndef FULL_PATH/* so that this can be specified from the Makefile */
58
  #define FULL_PATH	"/usr/sbin/larpe-reload-apache2-script"
59
#endif
60
#ifndef UMASK
61
  #define UMASK		077
62
#endif
63

  
64
/* END OF CONFIGURATION SECTION */
65

  
66
#if defined(__STDC__) && defined(__sgi)
67
#define environ _environ
68
#endif
69

  
70
/* don't change def_IFS */
71
char def_IFS[] = "IFS= \t\n";
72
/* you may want to change def_PATH, but you should really change it in */
73
/* your script */
74
#ifdef __sgi
75
char def_PATH[] = "PATH=/usr/bsd:/usr/bin:/bin:/usr/local/bin:/usr/sbin";
76
#else
77
char def_PATH[] = "PATH=/usr/ucb:/usr/bin:/bin:/usr/local/bin";
78
#endif
79
/* don't change def_CDPATH */
80
char def_CDPATH[] = "CDPATH=.";
81
/* don't change def_ENV */
82
char def_ENV[] = "ENV=:";
83

  
84
/*
85
  This function changes all environment variables that start with LD_
86
  into variables that start with XD_.  This is important since we
87
  don't want the script that is executed to use any funny shared
88
  libraries.
89

  
90
  The other changes to the environment are, strictly speaking, not
91
  needed here.  They can safely be done in the script.  They are done
92
  here because we don't trust the script writer (just like the script
93
  writer shouldn't trust the user of the script).
94
  If IFS is set in the environment, set it to space,tab,newline.
95
  If CDPATH is set in the environment, set it to ``.''.
96
  Set PATH to a reasonable default.
97
*/
98
void
99
clean_environ(void)
100
{
101
    char **p;
102
    extern char **environ;
103

  
104
    for (p = environ; *p; p++) {
105
	if (strncmp(*p, "LD_", 3) == 0)
106
	    **p = 'X';
107
	else if (strncmp(*p, "_RLD", 4) == 0)
108
	    **p = 'X';
109
	else if (strncmp(*p, "PYTHON", 6) == 0)
110
	    **p = 'X';
111
	else if (strncmp(*p, "IFS=", 4) == 0)
112
	    *p = def_IFS;
113
	else if (strncmp(*p, "CDPATH=", 7) == 0)
114
	    *p = def_CDPATH;
115
	else if (strncmp(*p, "ENV=", 4) == 0)
116
	    *p = def_ENV;
117
    }
118
    putenv(def_PATH);
119
}
120

  
121
int
122
main(int argc, char **argv)
123
{
124
    struct stat statb;
125
    gid_t egid = getegid();
126
    uid_t euid = geteuid();
127

  
128
    /*
129
      Sanity check #1.
130
      This check should be made compile-time, but that's not possible.
131
      If you're sure that you specified a full path name for FULL_PATH,
132
      you can omit this check.
133
    */
134
    if (FULL_PATH[0] != '/') {
135
	fprintf(stderr, "%s: %s is not a full path name\n", argv[0],
136
		FULL_PATH);
137
	fprintf(stderr, "You can only use this wrapper if you\n");
138
	fprintf(stderr, "compile it with an absolute path.\n");
139
	exit(1);
140
    }
141

  
142
    /*
143
      Sanity check #2.
144
      Check that the owner of the script is equal to either the
145
      effective uid or the super user.
146
    */
147
    if (stat(FULL_PATH, &statb) < 0) {
148
	perror("stat");
149
	exit(1);
150
    }
151
    if (statb.st_uid != 0 && statb.st_uid != euid) {
152
	fprintf(stderr, "%s: %s has the wrong owner\n", argv[0],
153
		FULL_PATH);
154
	fprintf(stderr, "The script should be owned by root,\n");
155
	fprintf(stderr, "and shouldn't be writeable by anyone.\n");
156
	exit(1);
157
    }
158

  
159
    if (setregid(egid, egid) < 0)
160
	perror("setregid");
161
    if (setreuid(euid, euid) < 0)
162
	perror("setreuid");
163

  
164
    clean_environ();
165

  
166
    umask(UMASK);
167

  
168
    while (**argv == '-')/* don't let argv[0] start with '-' */
169
	(*argv)++;
170
    execv(FULL_PATH, argv);
171
    fprintf(stderr, "%s: could not execute the script\n", argv[0]);
172
    exit(1);
173
}
larpe/branches/idwsf/larpe/Defaults.py
1
APP_DIR = "/var/lib/larpe"
2
DATA_DIR = "/usr/share/larpe"
3
ERROR_LOG = None #"/var/log/larpe.log"
4
WEB_ROOT = '/larpe'
larpe/branches/idwsf/larpe/__init__.py
1
import sys
2
import os
3
sys.path.insert(0, os.path.dirname(__file__))
4

  
5
import qommon
6

  
7
try:
8
    import lasso
9
except ImportError:
10
    lasso = None
11

  
12
if lasso and not hasattr(lasso, 'SAML2_SUPPORT'):
13
    lasso.SAML2_SUPPORT = False
14

  
larpe/branches/idwsf/larpe/admin/__init__.py
1
from root import RootDirectory
larpe/branches/idwsf/larpe/admin/apache.py
1
import os
2
import re
3
import urllib
4
import base64
5

  
6
from quixote import get_publisher, get_request
7

  
8
from larpe.hosts import Host
9
from Defaults import APP_DIR
10

  
11
def write_apache2_vhosts():
12
    hosts = Host.select(lambda x: x.name != 'larpe')
13
    hosts.sort()
14
    vhosts_dir = os.path.join(APP_DIR, 'vhosts.d')
15
    vhost_locations_dir = os.path.join(APP_DIR, 'vhost-locations.d')
16
    vhosts_dir_disabled = os.path.join(APP_DIR, 'vhosts.d.disabled')
17
    vhost_locations_dir_disabled = os.path.join(APP_DIR, 'vhost-locations.d.disabled')
18
    vhost_file_name = get_request().get_server().split(':')[0]
19
    vhost_file = None
20
    reversed_hostname = ''
21
    other_locations_hosts = []
22
    vhost = None
23

  
24
    if get_publisher().cfg.get(str('allow_config_generation'), True):
25
        vhost_file = open(os.path.join(vhosts_dir, vhost_file_name), 'w')
26
        locations_file = open(os.path.join(vhost_locations_dir, vhost_file_name), 'w')
27
    else:
28
        vhost_file = open(os.path.join(vhosts_dir_disabled, vhost_file_name), 'w')
29
        locations_file = open(os.path.join(vhost_locations_dir_disabled, vhost_file_name), 'w')
30
    try:
31
        main_vhost = open('/etc/apache2/sites-available/apache2-vhost-larpe', 'r')
32
        file_content = main_vhost.read()
33
        regexp = re.compile('<VirtualHost (.*?)>')
34
        vhost_ip = regexp.findall(file_content)[0]
35
    except:
36
        vhost_ip = '*'
37

  
38
    for host in hosts:
39
        if host.orig_site is None:
40
            # This site hasn't been fully configured
41
            continue
42
        if host.reversed_hostname != reversed_hostname and host.reversed_hostname != get_publisher().cfg['proxy_hostname']:
43
            if vhost is not None:
44
                vhost.close()
45
            vhost = Vhost(host, vhost_ip)
46
            vhost.write(vhost_file)
47
            reversed_hostname = host.reversed_hostname
48

  
49
        if host.reversed_hostname == get_publisher().cfg['proxy_hostname']:
50
            conf_file = locations_file
51
        else:
52
            conf_file = vhost_file
53

  
54
        Location(host).write(conf_file)
55

  
56
    if vhost_file is not None:
57
        if vhost is not None:
58
            vhost.close()
59
        vhost_file.close()
60
    if locations_file is not None:
61
        locations_file.close()
62

  
63
    if get_publisher().cfg.get(str('allow_config_generation'), True):
64
        os.system('/usr/sbin/larpe-reload-apache2')
65

  
66

  
67
class Vhost:
68
    def __init__(self, host, main_ip_port):
69
        self.host = host
70
        self.main_ip_port = main_ip_port
71
        self.conf_file = None
72

  
73
    def get_ip_port(self):
74
        if self.host.scheme == 'https':
75
            return self.main_ip_port.replace(':80', ':443')
76
        else:
77
            return self.main_ip_port.replace(':443', ':80')
78
    ip_port = property(get_ip_port)
79

  
80
    def get_proxy_url(self):
81
        if get_publisher().cfg.get('use_proxy', False) and self.host.use_proxy == True:
82
            return 'http://%(proxy_ip)s:%(proxy_port)s' % get_publisher().cfg
83
        return None
84
    proxy_url = property(get_proxy_url)
85

  
86
    def get_proxy_auth(self):
87
        if self.get_proxy_url() and get_publisher().cfg.get('proxy_user'):
88
            credentials = base64.encodestring(
89
                '%(proxy_user)s:%(proxy_password)s' % get_publisher().cfg)[:-1]
90
            return '"Basic %s"' % credentials
91
        return None
92
    proxy_auth = property(get_proxy_auth)
93

  
94
    def get_cfg(self):
95
        return { 'ip_port': self.ip_port,
96
                 'reversed_hostname': self.host.reversed_hostname,
97
                 'proxy_url': self.proxy_url,
98
                 'proxy_auth': self.proxy_auth }
99
    cfg = property(get_cfg)
100

  
101
    def write(self, conf_file):
102
        self.conf_file = conf_file
103
        conf_lines = []
104
        # Start Virtual Host
105
        conf_lines.append('<VirtualHost %(ip_port)s>' % self.cfg)
106
        # Server name and administrator 
107
        conf_lines.append('ServerName %(reversed_hostname)s' % self.cfg)
108
        conf_lines.append('# ServerAdmin root@localhost\n')
109
        # Include common vhost configuration
110
        conf_lines.append('include /usr/share/larpe/apache2.conf\n')
111
        # SSL
112
        if self.host.scheme == 'https':
113
            conf_lines.append('SSLEngine On\n')
114
        if self.host.orig_site.startswith('https'):
115
            conf_lines.append('SSLProxyEngine On\n')
116
        # Remote proxy configuration
117
        if self.proxy_url is not None:
118
            conf_lines.append('ProxyRemote * %(proxy_url)s' % self.cfg)
119
            if self.proxy_auth is not None:
120
                conf_lines.append('RequestHeader set Proxy-Authorization %(proxy_auth)s\n' % self.cfg)
121
        # Write it all
122
        conf_file.write('\n\t'.join(conf_lines))
123

  
124
    def close(self):
125
        if self.conf_file:
126
            self.conf_file.write('</VirtualHost>\n\n')
127

  
128

  
129
def apache_escape_chars(url):
130
    special_characters = ('\\', '.', '?', '*', '+', '^', '$', '|', '(', ')', '[', ']')
131
    for char in special_characters:
132
        url = url.replace(char, '\%s' % char)
133
    return url
134

  
135
class Location:
136
    def __init__(self, host):
137
        self.host = host
138

  
139
    def get_reversed_directory(self):
140
        if not self.host.reversed_directory:
141
            return '%s/' % get_request().environ['SCRIPT_NAME']
142
        else:
143
            return '%s/%s/' % (get_request().environ['SCRIPT_NAME'], self.host.reversed_directory)
144
    reversed_directory = property(get_reversed_directory)
145

  
146
    def get_python_path(self):
147
        if hasattr(self.host, 'apache_output_python_filters') and \
148
                hasattr(self.host, 'apache_python_paths') and self.host.apache_python_paths:
149
            python_path = 'PythonPath "sys.path'
150
            for path in self.host.apache_python_paths:
151
                python_path += "+['%s']" % path
152
            python_path += '"'
153
            return python_path
154
        else:
155
            return None
156
    python_path = property(get_python_path)
157

  
158
    def get_output_filters(self):
159
        python_filters = ''
160
        output_filters = []
161
        if hasattr(self.host, 'apache_output_python_filters'):
162
            i = 0
163
            for filter_file in self.host.apache_output_python_filters:
164
                filter_name = 'filter%d' % i
165
                python_filters += 'PythonOutputFilter %s %s\n\t\t' % (filter_file, filter_name)
166
                output_filters.append(filter_name)
167
                i += 1
168
        if hasattr(self.host, 'apache_output_filters'):
169
            for filter in self.host.apache_output_filters:
170
                output_filters.append(filter)
171
        if output_filters:
172
            return python_filters + 'SetOutputFilter ' + ';'.join(output_filters)
173
        else:
174
            return None
175
    output_filters = property(get_output_filters)
176

  
177
    def get_old_auth_url(self):
178
        old_auth_url = None
179
        if self.host.initiate_sso_url:
180
            old_auth_url = self.host.initiate_sso_url
181
        elif self.host.auth_url is not None:
182
            if self.host.auth_url.startswith('http://'):
183
                chars_to_skip = 5
184
            else:
185
                chars_to_skip = 6
186
            regexp = re.compile(self.host.orig_site[chars_to_skip:])
187
            old_auth_url_short = regexp.sub('', self.host.auth_url[chars_to_skip:])
188
            if old_auth_url_short.startswith('/'):
189
                old_auth_url = old_auth_url_short
190
            else:
191
                old_auth_url = '/' + old_auth_url_short
192
        if old_auth_url:
193
            old_auth_url = apache_escape_chars(old_auth_url)
194
        return old_auth_url
195
    old_auth_url = property(get_old_auth_url)
196

  
197
    def get_new_auth_url(self):
198
        if not hasattr(self.host, 'base_url'):
199
            return None
200
        base_url_tokens = self.host.base_url.split('/')
201
        base_url_tokens[-1] = 'login'
202
        return '/'.join(base_url_tokens)
203
    new_auth_url = property(get_new_auth_url)
204

  
205
    def get_old_logout_url(self):
206
        old_logout_url = None
207
        if self.host.logout_url is not None:
208
            if self.host.logout_url.startswith('http://'):
209
                chars_to_skip = 5
210
            else:
211
                chars_to_skip = 6
212
            regexp = re.compile(self.host.orig_site[chars_to_skip:])
213
            old_logout_url_short = regexp.sub('', self.host.logout_url[chars_to_skip:])
214
            if old_logout_url_short.startswith('/'):
215
                old_logout_url = old_logout_url_short
216
            else:
217
                old_logout_url = '/' + old_logout_url_short
218
            old_logout_url = apache_escape_chars(old_logout_url)
219
        return old_logout_url
220
    old_logout_url = property(get_old_logout_url)
221

  
222
    def get_new_logout_url(self):
223
        if not hasattr(self.host, 'base_url'):
224
            return None
225
        base_url_tokens = self.host.base_url.split('/')
226
        base_url_tokens[-1] = 'logout'
227
        return '/'.join(base_url_tokens)
228
    new_logout_url = property(get_new_logout_url)
229

  
230
    def get_orig_site_url_and_dir(self):
231
        # Split url
232
        if self.host.orig_site.startswith('http://'):
233
            orig_host, orig_query = urllib.splithost(self.host.orig_site[5:])
234
        else:
235
            orig_host, orig_query = urllib.splithost(self.host.orig_site[6:])
236
        # Add a trailing slash if necessary
237
        if self.host.orig_site.endswith('/'):
238
            orig_url = self.host.orig_site
239
            orig_dir = orig_query
240
        else:
241
            orig_url = self.host.orig_site + '/'
242
            orig_dir = orig_query + '/'
243
        return orig_url, orig_dir
244

  
245
    def get_orig_url(self):
246
        orig_url, orig_dir = self.get_orig_site_url_and_dir()
247
        return orig_url
248
    orig_url = property(get_orig_url)
249

  
250
    def get_orig_dir(self):
251
        orig_url, orig_dir = self.get_orig_site_url_and_dir()
252
        return orig_dir
253
    orig_dir = property(get_orig_dir)
254

  
255
    def get_cfg(self):
256
        return { 'reversed_directory': self.reversed_directory,
257
                 'old_auth_url': self.old_auth_url,
258
                 'new_auth_url': self.new_auth_url,
259
                 'old_logout_url': self.old_logout_url,
260
                 'new_logout_url': self.new_logout_url,
261
                 'orig_url': self.orig_url,
262
                 'orig_dir': self.orig_dir }
263
    cfg = property(get_cfg)
264

  
265
    def write(self, conf_file):
266
        conf_lines = []
267
        # Start Location
268
        conf_lines.append('\n\t<Location %(reversed_directory)s>' % self.cfg)
269
        # No user restriction
270
        conf_lines.append('Allow from all')
271
        # Apache output filters
272
        if self.python_path:
273
            conf_lines.append(self.python_path)
274
        if self.output_filters:
275
            conf_lines.append(self.output_filters)
276
        # Enable rewrite module
277
        if self.old_auth_url is not None or self.old_logout_url is not None:
278
            conf_lines.append('RewriteEngine On')
279
        # Redirect old authentication url to the new one
280
        if self.old_auth_url is not None and self.host.auth_form_places == 'form_once':
281
            conf_lines.append(
282
                'RewriteRule         %(old_auth_url)s    %(new_auth_url)s  [R]' % self.cfg)
283
        # Redirect old logout url to the new one
284
        if self.old_logout_url is not None:
285
            conf_lines.append(
286
                'RewriteRule         %(old_logout_url)s    %(new_logout_url)s  [R]' % self.cfg)
287
        # Redirect the home page to the login page
288
        if self.host.redirect_root_to_login is True:
289
            conf_lines.append('RewriteRule         /$        %(new_auth_url)s  [R]' % self.cfg)
290
        # Convert urls in http headers to/from the new domain
291
        conf_lines.append('ProxyPass           %(orig_url)s' % self.cfg)
292
        conf_lines.append('ProxyPassReverse    %(orig_url)s' % self.cfg)
293
        # Convert urls in html pages to/from the new domain
294
        conf_lines.append('ProxyHTMLURLMap     %(orig_dir)s     %(reversed_directory)s' % self.cfg)
295
        conf_lines.append('ProxyHTMLURLMap     %(orig_url)s    %(reversed_directory)s' % self.cfg)
296
        # Write it all and close the Location
297
        conf_file.write('\n\t\t'.join(conf_lines))
298
        conf_file.write('\n\t</Location>\n')
299

  
larpe/branches/idwsf/larpe/admin/fields_prefill.ptl
1
from quixote import get_response, redirect
2
from quixote.directory import Directory
3

  
4
from qommon.form import *
5

  
6
from field_prefill import FieldPrefill
7
from menu import html_top, command_icon
8

  
9
class FieldUI:
10
    def __init__(self, field_prefill):
11
        self.field_prefill = field_prefill
12

  
13
    def form_edit(self):
14
        form = Form(enctype='multipart/form-data')
15
        form.add(StringWidget, 'name', title = _('Field name'), required = True,
16
            size = 50, value = self.field_prefill.name)
17
        form.add(StringWidget, 'xpath', title = _('Xpath of the attribute'), required = True,
18
            size = 50, value = self.field_prefill.xpath, hint=_('Example: /pp:PP/pp:InformalName'))
19
        form.add(IntWidget, 'number', title = _('Number of the field in the data'), required = True,
20
            size = 3, value = self.field_prefill.number,
21
            hint=_('Change it if there are multiple fields corresponding to the same Xpath and you want to get another than the first one'))
22
        form.add(CheckboxWidget, 'raw_xml', title=_('Get raw XML value'),
23
            value = self.field_prefill.raw_xml)
24
        form.add(StringWidget, 'regexp_match', title = _('Python regexp of a string to match'),
25
            size = 50, value = self.field_prefill.regexp_match)
26
        form.add(StringWidget, 'regexp_replacing', title = _('Python regexp of the replacing string'),
27
            size = 50, value = self.field_prefill.regexp_replacing)
28
        form.add(WidgetDict, 'select_options', title = _('Options mapping for a select field'),
29
                value = self.field_prefill.select_options, add_element_label = _('Add item'))
30
        form.add_submit('submit', _('Submit'))
31
        form.add_submit('cancel', _('Cancel'))
32
        return form
33

  
34
    def submit_edit(self, form):
35
        for f in ('name', 'xpath', 'number', 'raw_xml',  'regexp_match', 'regexp_replacing', 'select_options'):
36
            widget = form.get_widget(f)
37
            setattr(self.field_prefill, f, widget.parse())
38
        self.field_prefill.store()
39

  
40
class FieldPage(Directory):
41
    _q_exports = ['', 'delete']
42

  
43
    def __init__(self, field_id):
44
        self.field_prefill = FieldPrefill.get(field_id)
45
        self.field_ui = FieldUI(self.field_prefill)
46
        get_response().breadcrumb.append((field_id + '/', field_id))
47

  
48
    def _q_index [html] (self):
49
        form = self.field_ui.form_edit()
50
        redo = False
51

  
52
        if form.get_widget('cancel').parse():
53
            return redirect('..')
54

  
55
        if form.get_widget('select_options') and form.get_widget('select_options').get_widget('add_element').parse():
56
            form.clear_errors()
57
            redo = True
58

  
59
        if redo is False and form.is_submitted() and not form.has_errors():
60
            self.field_ui.submit_edit(form)
61
            return redirect('..')
62

  
63
        get_response().breadcrumb.append( ('edit', _('Edit')) )
64
        html_top('edit', title = _('Edit'))
65
        '<h2>%s</h2>' % _('Edit')
66

  
67
        form.render()
68

  
69
    def delete [html] (self):
70
        form = Form(enctype='multipart/form-data')
71
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
72
                        'You are about to irrevocably delete this field.')))
73
        form.add_submit('submit', _('Submit'))
74
        form.add_submit('cancel', _('Cancel'))
75
        if form.get_widget('cancel').parse():
76
            return redirect('..')
77
        if not form.is_submitted() or form.has_errors():
78
            get_response().breadcrumb.append(('delete', _('Delete')))
79
            html_top('delete_form', title = _('Delete Field'))
80
            '<h2>%s : %s</h2>' % (_('Delete Field'), self.field_prefill.id)
81
            form.render()
82
        else:
83
            self.field_prefill.remove_self()
84
            return redirect('..')
85

  
86

  
87
class FieldsDirectory(Directory):
88
    _q_exports = ['', 'new']
89

  
90
    def __init__(self, form_prefill):
91
        get_response().breadcrumb.append(('fields/', _('Fields')))
92
        self.form_prefill = form_prefill
93

  
94
    def _q_lookup(self, component):
95
        return FieldPage(component)
96

  
97
    def _q_index [html] (self):
98
        html_top('fields', title = _('Fields'))
99
        """<ul id="nav-fields-admin">
100
          <li><a href="new">%s</a></li>
101
        </ul>""" % _('New Field')
102

  
103
        '<ul class="biglist">'
104

  
105
        for field_prefill in FieldPrefill.select(lambda x: x.form_id == self.form_prefill.id):
106
            if not field_prefill.name:
107
                continue
108

  
109
            # Split too long xpath
110
            xpath = field_prefill.xpath
111
            xpath_tokens = xpath.split(str('/'))
112
            if len(xpath_tokens) > 3:
113
                xpath = str('.../') + str('/').join(xpath_tokens[-3:])
114

  
115
            '<li>'
116
            '<strong class="label">%s</strong>' % field_prefill.name
117
            '<br />%s' % xpath
118
            '<p class="commands">'
119
            command_icon('%s/' % field_prefill.id, 'edit')
120
            command_icon('%s/delete' % field_prefill.id, 'remove')
121
            '</p></li>'
122
        '</ul>'
123

  
124
    def new [html] (self):
125
        get_response().breadcrumb.append(('new', _('New')) )
126
        field_prefill = FieldPrefill()
127
        field_prefill.form_id = self.form_prefill.id
128
        field_prefill.store()
129
        return redirect('%s/' % field_prefill.id)
130

  
larpe/branches/idwsf/larpe/admin/forms_prefill.ptl
1
from quixote import get_response, redirect
2
from quixote.directory import Directory
3

  
4
from qommon.form import *
5

  
6
from form_prefill import FormPrefill
7
from fields_prefill import FieldsDirectory
8
from menu import html_top, command_icon
9

  
10
class FormUI:
11
    def __init__(self, form_prefill):
12
        self.form_prefill = form_prefill
13

  
14
    def form_edit(self):
15
        form = Form(enctype='multipart/form-data')
16
        form.add(StringWidget, 'name', title = _('Form name'), required = True,
17
            size = 50, value = self.form_prefill.name, hint=_('Only used for display'))
18
        form.add(UrlWidget, 'url', title = _('Form address'), required = True,
19
            size = 50, value = self.form_prefill.url)
20
        form.add(StringWidget, 'profile', title = _('ID-WSF data profile'), required = True,
21
            size = 50, value = self.form_prefill.profile, hint=_('Example: urn:liberty:id-sis-pp:2005-05'))
22
        form.add(StringWidget, 'prefix', title = _('ID-WSF data XML prefix'), required = True,
23
            size = 50, value = self.form_prefill.prefix, hint=_('Example: pp'))
24
        form.add_submit('submit', _('Submit'))
25
        form.add_submit('cancel', _('Cancel'))
26
        return form
27

  
28
    def submit_edit(self, form):
29
        for f in ('name', 'url', 'profile', 'prefix'):
30
            widget = form.get_widget(f)
31
            setattr(self.form_prefill, f, widget.parse())
32
        self.form_prefill.store()
33

  
34
class FormPage(Directory):
35
    _q_exports = ['', 'edit', 'delete']
36

  
37
    def __init__(self, form_id):
38
        self.form_prefill = FormPrefill.get(form_id)
39
        self.form_ui = FormUI(self.form_prefill)
40
        get_response().breadcrumb.append((form_id + '/', form_id))
41

  
42
    def _q_index [html] (self):
43
        html_top('forms_prefill', title = 'Form prefilling')
44

  
45
        '<h2>%s</h2>' % _('Form prefilling configuration')
46
        '<dl>'
47
        '<dt><a href="edit">%s</a></dt> <dd>%s</dd>' % (
48
                _('Edit'), _('Configure this form'))
49
        '<dt><a href="fields/">%s</a></dt> <dd>%s</dd>' % (
50
                _('Fields'), _('Configure the fields of this form'))
51
        '</dl>'
52

  
53
    def _q_lookup(self, component):
54
        if component == 'fields':
55
            return FieldsDirectory(self.form_prefill)
56

  
57
    def edit [html] (self):
58
        form = self.form_ui.form_edit()
59
        if form.get_widget('cancel').parse():
60
            return redirect('.')
61

  
62
        if form.is_submitted() and not form.has_errors():
63
            self.form_ui.submit_edit(form)
64
            return redirect('.')
65

  
66
        get_response().breadcrumb.append( ('edit', _('Edit')) )
67
        html_top('edit', title = _('Edit'))
68
        '<h2>%s</h2>' % _('Edit')
69

  
70
        form.render()
71

  
72
    def delete [html] (self):
73
        form = Form(enctype='multipart/form-data')
74
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
75
                        'You are about to irrevocably delete this form.')))
76
        form.add_submit('submit', _('Submit'))
77
        form.add_submit('cancel', _('Cancel'))
78
        if form.get_widget('cancel').parse():
79
            return redirect('..')
80
        if not form.is_submitted() or form.has_errors():
81
            get_response().breadcrumb.append(('delete', _('Delete')))
82
            html_top('delete_form', title = _('Delete Form'))
83
            '<h2>%s : %s</h2>' % (_('Delete Form'), self.form_prefill.id)
84
            form.render()
85
        else:
86
            self.form_prefill.remove_self()
87
            return redirect('..')
88

  
89

  
90
class FormsDirectory(Directory):
91
    _q_exports = ['', 'new']
92

  
93
    def __init__(self, host):
94
        get_response().breadcrumb.append(('forms_prefill/', _('Forms')))
95
        self.host = host
96

  
97
    def _q_lookup(self, component):
98
        return FormPage(component)
99

  
100
    def _q_index [html] (self):
101
        html_top('forms', title = _('Forms'))
102
        """<ul id="nav-forms-admin">
103
          <li><a href="new">%s</a></li>
104
        </ul>""" % _('New Form')
105

  
106
        '<ul class="biglist">'
107

  
108
        for form_prefill in FormPrefill.select(lambda x: x.host_id == self.host.id):
109
            if not form_prefill.name:
110
                continue
111
            '<li>'
112
            '<strong class="label">%s</strong>' % form_prefill.name
113
            url = form_prefill.url
114
            '<br /><a href="%s">%s</a>' % (url, url)
115
            '<p class="commands">'
116
            command_icon('%s/' % form_prefill.id, 'edit')
117
            command_icon('%s/delete' % form_prefill.id, 'remove')
118
            '</p></li>'
119
        '</ul>'
120

  
121
    def new [html] (self):
122
        get_response().breadcrumb.append(('new', _('New')) )
123
        form_prefill = FormPrefill()
124
        form_prefill.host_id = self.host.id
125
        form_prefill.store()
126
        return redirect('%s/edit' % form_prefill.id)
127

  
larpe/branches/idwsf/larpe/admin/hosts.ptl
1
import os
2
import urllib
3
import urlparse
4

  
5
from quixote import redirect, get_request, get_response, get_publisher
6
from quixote.directory import Directory
7

  
8
import lasso
9

  
10
from larpe.qommon import get_cfg
11
from larpe.qommon.form import *
12
from larpe.qommon.misc import http_get_page, get_abs_path
13

  
14
import site_authentication
15
from larpe import errors
16
from larpe import misc
17
from larpe.hosts import Host
18
from larpe.admin.liberty_utils import *
19
#from larpe.filter import filter_misc
20
from larpe.admin.apache import write_apache2_vhosts
21
from larpe.admin.forms_prefill import FormsDirectory
22

  
23
from menu import *
24

  
25
def check_basic_configuration(form):
26
    get_publisher().reload_cfg()
27
    # Check reversed_hostname and reversed_directory
28
    reversed_hostname = form.get_widget('reversed_hostname').parse()
29
    reversed_directory = form.get_widget('reversed_directory').parse()
30
    if reversed_hostname == get_publisher().cfg['proxy_hostname'] and not reversed_directory:
31
        form.set_error('reversed_hostname',
32
            _('You must either choose a different hostname from Larpe or specify a reversed directory'))
33

  
34
def check_minimal_configuration(form):
35
    # Check auth_url and auth_form_page_url
36
    auth_url = form.get_widget('auth_url').parse()
37
    auth_form_page_url = form.get_widget('auth_form_page_url').parse()
38
    if auth_url and auth_form_page_url:
39
        form.set_error('auth_form_page_url',
40
            _('"Authentication page" and "Authentication form page" are incompatible. Only fill one of them.'))
41

  
42
def convert_label_to_name(label):
43
    '''Build host name from host label'''
44
    name = label.lower()
45
    invalid_characters = [' ', "'"]
46
    for char in invalid_characters:
47
        name = name.replace(char, '_')
48
    return name
49

  
50
class DictWidget(Widget):
51
    def render_content [html] (self):
52
        self.render_br = False
53
        if self.value['enabled'] is True:
54
            htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled', checked='checked')
55
        else:
56
            htmltag('input', xml_end=True, type='checkbox', name=self.name + '_enabled')
57
        ' ' + self.name + ' &nbsp;'
58
        htmltag('input', xml_end=True, type='text', name=self.name, value=self.value['value'], size='35', **self.attrs)
59

  
60
    def _parse(self, request):
61
        enabled = request.form.get(self.name + '_enabled')
62
        value = request.form.get(self.name)
63
        self.value = { 'enabled': enabled, 'value': value }
64

  
65

  
66
class ConfigurationAssistant(Directory):
67
    _q_exports = ['start', 'check_new_address', 'modify_site_address_and_name', 'authentication_and_logout_adresses',
68
                  'check_auto_detected_configuration', 'credentials', 'check_authentication', 'send_authentication_request',
69
                  'see_authentication_response', 'see_response_html_page', 'authentication_success_criteria',
70
                  'modify_authentication_request', 'auth_request_post_parameters', 'auth_request_http_headers',
71
                  'sso_init_link', 'metadatas', 'check_full_configuration', 'advanced_options']
72

  
73
    def __init__(self, host):
74
        self.host = host
75

  
76
    def start [html] (self):
77
        # Check the global domain name has been previously set
78
        get_publisher().reload_cfg()
79
        if not get_cfg('domain_names') or not get_cfg('domain_names')[0]:
80
            html_top('preamble', title=_('Need domain name configuration'))
81
            return htmltext(_('Before configuring hosts, you must <a href="../../../settings/domain_names">setup a global domain name</a> in %(settings)s menu.') % { 'settings': _('Settings') })
82

  
83
        form = self.form_start()
84

  
85
        if form.get_widget('cancel').parse():
86
            return redirect('../..')
87

  
88
        connection_failure = None
89
        if form.is_submitted() and not form.has_errors():
90
            try:
91
                self.submit_start_form(form)
92
            except Exception, e:
93
                connection_failure = e
94
            else:
95
                return redirect('check_new_address')
96

  
97
        html_top('step1', title=_('Step 1 - Basic configuration'))
98
        '<h2>%s</h2>' % _('Step 1 - Basic configuration')
99

  
100
        if connection_failure:
101
            '<div class="errornotice">%s</div>' % connection_failure
102

  
103
        form.render()
104

  
105
    def form_start(self):
106
        form = Form(enctype='multipart/form-data')
107
        form.add(UrlWidget, 'orig_site', title = _('Original site root address'), required = True,
108
                size = 50, value = self.host.orig_site,
109
                hint = _('If your site address is http://test.org/index.php, put http://test.org/ here'))
110
        get_publisher().reload_cfg()
111
        if get_cfg('use_proxy'):
112
            form.add(CheckboxWidget, 'use_proxy', title = _('Use a proxy'),
113
                    hint = _("Uncheck it if Larpe doesn't need to use the proxy to connect to this site"),
114
                    value = self.host.use_proxy)
115
        else:
116
            form.add(HtmlWidget, htmltext('<p>%s</p>' % \
117
                _('If Larpe needs to use a proxy to connect to this site, you must first configure it in <a href="../../../settings/proxy">global proxy parameters</a>.')))
118
        form.add_submit('cancel', _('Cancel'))
119
        form.add_submit('submit', _('Next'))
120
        return form
121

  
122
    def submit_start_form(self, form):
123
        fields = ['orig_site']
124
        if get_cfg('use_proxy'):
125
            fields += ['use_proxy']
126
        for f in fields:
127
            setattr(self.host, f, form.get_widget(f).parse())
128

  
129
        # If no proxy is setup yet, set use_proxy to False for new hosts in case a proxy is later configured
130
        if not get_cfg('use_proxy'):
131
            self.host.use_proxy = False
132

  
133
        # Remove what is after the last '/'
134
        #self.host.orig_site = '/'.join(self.host.orig_site.split('/')[:-1])
135

  
136
        html_page = self.get_data_after_redirects(self.host.orig_site)
137

  
138
        if not self.host.label:
139
            # Look for html title in original site index page
140
            regexp = re.compile("""<title.*?>(.*?)</title>""", re.DOTALL | re.IGNORECASE)
141
            title = regexp.findall(html_page)
142
            if title:
143
                self.host.label = title[0]
144
            else:
145
                self.host.label = 'Untitled'
146
            # If another site already uses this site title, add trailings "_" until we find an available name
147
            existing_label = True
148
            while existing_label is True:
149
                for any_host in Host.select():
150
                    if any_host.id != self.host.id and self.host.label == any_host.label:
151
                        self.host.label += '_'
152
                        break
153
                else:
154
                    existing_label = False
155

  
156
            # Fill host.name attribute
157
            self.host.name = convert_label_to_name(self.host.label)
158

  
159
        if not self.host.scheme:
160
            # Get tokens from orig site url
161
            orig_scheme, rest = urllib.splittype(self.host.orig_site)
162
            orig_host, rest = urllib.splithost(rest)
163

  
164
            get_publisher().reload_cfg()
165
            # Set url scheme (HTTP or HTTPS)
166
            # TODO: Handle the option "Both"
167
            if get_cfg('sites_url_scheme'):
168
                self.host.scheme = get_cfg('sites_url_scheme')
169
            else:
170
                self.host.scheme = orig_scheme
171

  
172
        if not self.host.reversed_hostname:
173
            # Build a new domain name
174
            short_name = orig_host.split('.')[0]
175
            if short_name == 'www':
176
                short_name = orig_host.split('.')[1]
177
            self.host.reversed_hostname = '%s.%s' % (short_name, get_cfg('domain_names')[0])
178
            # If another site already uses this domain name, add some trailing "_" until we find an available name
179
            existing_domain = True
180
            while existing_domain is True:
181
                for any_host in Host.select():
182
                    if any_host.id != self.host.id and self.host.reversed_hostname == any_host.reversed_hostname:
183
                        self.host.reversed_hostname += '-'
184
                        break
185
                else:
186
                    existing_domain = False
187
            self.host.reversed_directory = None
188

  
189
        if not self.host.new_url:
190
            # New url for this host
191
            self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
192
            # FIXME: Check if the new domain name already exists
193

  
194
            # New url for this host
195
            #        self.host.new_url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
196
            #        if self.host.reversed_directory is not None:
197
            #            self.host.new_url += '%s/' % self.host.reversed_directory
198

  
199
        self.host.store()
200
        write_apache2_vhosts()
201

  
202
    # XXX: Should use the FancyURLopener class instead when it supports proxies
203
    def get_data_after_redirects(self, start_url):
204
        if not start_url:
205
            return ''
206
        status = 302
207
        location = None
208
        while status // 100 == 3:
209
            if location is None:
210
                url = start_url
211
            elif location.startswith('http'):
212
                # Location is an absolute path
213
                url = location
214
            else:
215
                # Location is a relative path
216
                url = urlparse.urljoin(start_url, location)
217
            response, status, data, auth_headers = http_get_page(url, use_proxy=self.host.use_proxy)
218
            location = response.getheader('Location', None)
219
        return data
220

  
221
    def create_dirs(self):
222
        # Hack : sites must use the configuration which is stored in main Larpe directory,
223
        # but they need to have a directory named with their hostname, which will contain the
224
        # main domain name for Larpe so they know where is the main configuration
225
        hostname_dir = get_abs_path(os.path.join('..', self.host.reversed_hostname))
226
        if not os.path.exists(hostname_dir):
227
            os.mkdir(hostname_dir)
228
        # Load the configuration from the main directory
229
        get_publisher().reload_cfg()
230
        # Write it in the site directory
231
        get_publisher().write_cfg(hostname_dir)
232

  
233
        # Storage directories
234
        if not self.host.reversed_directory:
235
            reversed_dir = 'default'
236
        else:
237
            reversed_dir = self.host.reversed_directory
238
        self.host.site_dir = \
239
            os.path.join(get_publisher().app_dir, 'sp', self.host.reversed_hostname, reversed_dir)
240
        user_dir = os.path.join(self.host.site_dir, 'users')
241
        token_dir = os.path.join(self.host.site_dir, 'tokens')
242
        filter_dir = os.path.join(self.host.site_dir, 'filters')
243
        for dir in (self.host.site_dir, user_dir, token_dir, filter_dir):
244
            if not os.path.isdir(dir):
245
                os.makedirs(dir)
246

  
247
    def generate_ssl_keys(self):
248
        # Generate SSL keys
249
        private_key_path = os.path.join(self.host.site_dir, 'private_key.pem')
250
        public_key_path = os.path.join(self.host.site_dir, 'public_key.pem')
251
        if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
252
            set_provider_keys(private_key_path, public_key_path)
253
        self.host.private_key = private_key_path
254
        self.host.public_key = public_key_path
255

  
256
    def generate_metadatas(self):
257
        metadata_cfg = {}
258

  
259
        # Organization name
260
        self.host.organization_name = self.host.label
261
        metadata_cfg['organization_name'] = self.host.organization_name
262

  
263
        # Base URL
264
        base_url = '%s://%s%s/liberty/%s/liberty' % (self.host.scheme,
265
                                                     self.host.reversed_hostname,
266
                                                     get_request().environ['SCRIPT_NAME'],
267
                                                     self.host.name)
268
        metadata_cfg['base_url'] = base_url
269
        self.host.base_url = base_url
270

  
271
        if lasso.SAML2_SUPPORT:
272
            saml2_base_url = '%s://%s%s/liberty/%s/saml' % (self.host.scheme,
273
                                                         self.host.reversed_hostname,
274
                                                         get_request().environ['SCRIPT_NAME'],
275
                                                         self.host.name)
276
            metadata_cfg['saml2_base_url'] = saml2_base_url
277
            self.host.saml2_base_url = saml2_base_url
278

  
279
        # Provider Id
280
        provider_id = '%s/metadata' % base_url
281
        metadata_cfg['provider_id'] = provider_id
282
        self.host.provider_id = provider_id
283

  
284
        if lasso.SAML2_SUPPORT:
285
            saml2_provider_id = '%s/metadata' % saml2_base_url
286
            metadata_cfg['saml2_provider_id'] = saml2_provider_id
287
            self.host.saml2_provider_id = saml2_provider_id
288

  
289
        # Read public key
290
        public_key = ''
291
        if self.host.public_key is not None and os.path.exists(self.host.public_key):
292
            metadata_cfg['signing_public_key'] = open(self.host.public_key).read()
293

  
294
        # Write metadatas
295
        metadata_path = os.path.join(self.host.site_dir, 'metadata.xml')
296
        open(metadata_path, 'w').write(get_metadata(metadata_cfg))
297
        self.host.metadata = metadata_path
298

  
299
        if lasso.SAML2_SUPPORT:
300
            saml2_metadata_path = os.path.join(self.host.site_dir, 'saml2_metadata.xml')
301
            open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
302
            self.host.saml2_metadata = saml2_metadata_path
303

  
304
    def check_new_address [html] (self):
305
        form = Form(enctype='multipart/form-data')
306
        form.add_submit('cancel', _('Previous'))
307
        form.add_submit('submit', _('Next'))
308

  
309
        if form.get_widget('cancel').parse():
310
            return redirect('start')
311

  
312
        if form.is_submitted():
313
            self.create_dirs()
314
            if self.host.private_key is None:
315
                self.generate_ssl_keys()
316
            self.generate_metadatas()
317
            self.host.store()
318
            return redirect('authentication_and_logout_adresses')
319

  
320
        html_top('step2', title=_('Step 2 - Check the new site address works'))
321
        '<h2>%s</h2>' % _('Step 2 - Check the new site address works')
322

  
323
        htmltext(_('''\
324
<p>Before opening the following link, ensure you have configured your DNS for this address. If you don't 
325
have a DNS server and you just want to test Larpe, add this domain name in the file "/etc/hosts".</p>
326
<p>Then you can open this link in a new window or tab and see if your site is displayed. If it's ok, 
327
you can click the "%(next)s" button. Otherwise, click the "%(previous)s" button and check your settings.</p>
328
''') % {'next': _('Next'), 'previous': _('Previous')})
329
        '<p>'
330
        htmltext(_('The new address of this site is '))
331
        '<a href="%s">%s</a><br/>' % (self.host.new_url, self.host.new_url)
332
        htmltext(_('The name of this site is "%s".') % self.host.label)
333
        '</p><p>'
334
        htmltext(_('You can also <a href="modify_site_address_and_name">modify the address or the name of this site</a>'))
335
        '</p>'
336

  
337
        form.render()
338

  
339
    def modify_site_address_and_name [html] (self):
340
        form = self.form_modify_site_address_and_name()
341

  
342
        if form.get_widget('cancel').parse():
343
            return redirect('check_new_address')
344

  
345
        if form.is_submitted() and not form.has_errors():
346
            label = form.get_widget('label').parse()
347
            name = convert_label_to_name(label)
348
            for any_host in Host.select():
349
                if any_host.id != self.host.id and name == any_host.name:
350
                    form.set_error('label', _('An host with the same name already exists'))
351
                    break
352

  
353
        if form.is_submitted() and not form.has_errors():
354
            self.submit_modify_site_address_and_name_form(form)
355
            return redirect('check_new_address')
356

  
357
        html_top('modify_site_address_and_name', title=_('Modify site address and name'))
358
        '<h2>%s</h2>' % _('Modify site address and name')
359
        form.render()
360

  
361
    def form_modify_site_address_and_name(self):
362
        form = Form(enctype='multipart/form-data')
363
        form.add(UrlWidget, 'new_url', title = _('Address'), required = True,
364
                size = 50, value = self.host.new_url)
365
        form.add(StringWidget, 'label', title = _('Name'), required = True,
366
                size = 50, value = self.host.label)
367
        form.add_submit('submit', _('Submit'))
368
        form.add_submit('cancel', _('Cancel'))
369
        return form
370

  
371
    def submit_modify_site_address_and_name_form(self, form):
372
        fields = ['new_url', 'label']
373
        for f in fields:
374
            setattr(self.host, f, form.get_widget(f).parse())
375

  
376
        # Split url to retrieve components
377
        tokens = urlparse.urlparse(self.host.new_url)
378
        self.host.scheme = tokens[0]
379
        self.host.reversed_hostname = tokens[1]
380
        self.host.reversed_directory = tokens[2]
381
        if self.host.reversed_directory.startswith('/'):
382
            self.host.reversed_directory = self.host.reversed_directory[1:]
383

  
384
        # Fill host.name attribute
385
        self.host.name = convert_label_to_name(self.host.label)
386

  
387
        self.host.store()
388
        write_apache2_vhosts()
389

  
390
    def authentication_and_logout_adresses [html] (self):
391
        form = self.form_authentication_and_logout_adresses()
392

  
393
        if form.get_widget('cancel').parse():
394
            return redirect('check_new_address')
395

  
396
        if form.is_submitted() and not form.has_errors():
397
            self.submit_authentication_and_logout_adresses_form(form)
398
            return redirect('check_auto_detected_configuration')
399

  
400
        html_top('step3', title=_('Step 3 - Configure authentication and logout pages'))
401
        '<h2>%s</h2>' % _('Step 3 - Configure authentication and logout pages')
402
        form.render()
403

  
404
    def form_authentication_and_logout_adresses(self):
405
        form = Form(enctype='multipart/form-data')
406
        form.add(ValidUrlWidget, 'auth_url', title = _('Authentication form page address'),
407
                hint = _('Address of a page on the site which contains the authentication form'),
408
                required = True, size = 50, value = self.host.auth_url)
409
        form.add(ValidUrlWidget, 'logout_url', title = _('Logout address'), required = False,
410
                hint = _('Address of the logout link on the site'),
411
                size = 50, value = self.host.logout_url)
412
        form.add_submit('cancel', _('Previous'))
413
        form.add_submit('submit', _('Next'))
414
        return form
415

  
416
    def submit_authentication_and_logout_adresses_form(self, form):
417
        fields = ['auth_url', 'logout_url']
418
        for f in fields:
419
            setattr(self.host, f, form.get_widget(f).parse())
420
        self.host.auth_form_url = self.host.auth_url
421

  
422
        if not self.host.http_headers:
423
            self.host.http_headers = {
424
                'Content-Type': { 'enabled': True, 'value': 'application/x-www-form-urlencoded', 'immutable': False },
425
                'X-Forwarded-For': { 'enabled': True, 'value': _('(computed automatically)'), 'immutable': True },
426
                'X-Forwarded-Host': { 'enabled': True, 'value': self.host.reversed_hostname, 'immutable': False },
427
            }
428

  
429
        self.auto_detect_configuration()
430
        self.host.store()
431
        write_apache2_vhosts()
432

  
433
    def check_auto_detected_configuration [html] (self):
434
        form = Form(enctype='multipart/form-data')
435
        form.add_submit('cancel', _('Previous'))
436
        form.add_submit('submit', _('Next'))
437

  
438
        if form.get_widget('cancel').parse():
439
            return redirect('authentication_and_logout_adresses')
440

  
441
        if form.is_submitted():
442
            return redirect('credentials')
443

  
444
        html_top('step4', title=_('Step 4 - Check automatically detected configuration for the authentication form'))
445
        '<h2>%s</h2>' % _('Step 4 - Check automatically detected configuration for the authentication form')
446

  
447
        host_attrs = (
448
            ('auth_check_url', _('Address where the authentication form must be sent')),
449
            ('login_field_name', _('Name of the login field')),
450
            ('password_field_name', _('Name of the password field')),
451
        )
452

  
453
        html_fields = ''
454
        success = True
455
        for attr, name in host_attrs:
456
            color = 'black'
457
            if attr in ('auth_check_url', 'login_field_name', 'password_field_name') and \
458
                    not getattr(self.host, str(attr)):
459
                color = 'red'
460
                success = False
461
            html_fields += '<div style="margin-bottom: 0.1em; font-weight: bold; color: %s;">%s</div>' % (color, name)
462
            html_fields += '<div style="margin-left: 1em; margin-bottom: 0.3em; color: %s;">%s</div>' % \
463
                (color, getattr(self.host, str(attr)))
464
            if getattr(self.host, str(attr)) == '':
465
                html_fields += '<br />'
466

  
467
        '<p>'
468
        if success:
469
            htmltext(_('''\
470
The following authentication form parameters have been detected. If they look right, you can go to the next step.
471
If you think they are wrong, go back and check your settings then try again.
472
'''))
473
        else:
474
            htmltext(_('''\
475
The following authentication form parameters in red haven't been correctly detected. Go back and check
476
your settings then try again.
477
'''))
478
        '</p>'
479

  
480
        html_fields
481
        form.render()
482

  
483
    def credentials [html] (self):
484
        form = self.form_credentials()
485

  
486
        if form.get_widget('cancel').parse():
487
            return redirect('check_auto_detected_configuration')
488

  
489
        if form.is_submitted() and not form.has_errors():
490
            self.submit_credentials_form(form)
491
            return redirect('send_authentication_request')
492

  
493
        html_top('step5', title=_('Step 5 - Fill in a valid username/password for this site'))
494
        '<h2>%s</h2>' % _('Step 5 - Fill in a valid username/password for this site')
495
        form.render()
496

  
497
    def form_credentials(self):
498
        form = Form(enctype='multipart/form-data')
499
        form.add(StringWidget, 'username', title = _('Username'), required = True,
500
                size = 30, value = self.host.valid_username)
501
        form.add(PasswordWidget, 'password', title = _('Password'), required = True,
502
                size = 30, value = self.host.valid_password)
503
        for name, values in self.host.select_fields.iteritems():
504
            options = []
505
            if values:
506
                for value in values:
507
                    options.append(value)
508
                form.add(SingleSelectWidget, name, title = name.capitalize(),
509
                    value = values[0], options = options)
510
        form.add_submit('cancel', _('Previous'))
511
        form.add_submit('submit', _('Next'))
512
        return form
513

  
514
    def submit_credentials_form(self, form):
515
        self.host.valid_username = form.get_widget('username').parse()
516
        self.host.valid_password = form.get_widget('password').parse()
517
        self.host.valid_select = {}
518
        for name, values in self.host.select_fields.iteritems():
519
            if form.get_widget(name):
520
                self.host.valid_select[name] = form.get_widget(name).parse()
521

  
522
        self.host.store()
523

  
524
    def send_authentication_request(self):
525
        site_auth = site_authentication.get_site_authentication(self.host)
526
        self.host.auth_request_status, self.host.auth_request_data = site_auth.local_auth_check_dispatch(
527
            self.host.valid_username, self.host.valid_password, self.host.valid_select)
528
        self.host.auth_request_success, self.host.auth_request_return_content = \
529
            site_auth.check_auth(self.host.auth_request_status, self.host.auth_request_data)
530

  
531
        self.host.store()
532

  
533
        return redirect('check_authentication')
534

  
535
    def check_authentication [html] (self):
536
        form = Form(enctype='multipart/form-data')
537
        form.add_submit('cancel', _('Previous'))
538
        form.add_submit('submit', _('Next'))
539

  
540
        if form.get_widget('cancel').parse():
541
            return redirect('credentials')
542

  
543
        if form.is_submitted():
544
            return redirect('sso_init_link')
545

  
546
        html_top('step6', title=_('Step 6 - Check the authentication process'))
547
        '<h2>%s</h2>' % _('Step 6 - Check the authentication process')
548

  
549
        if self.host.auth_request_success:
550
            htmltext(_('''<p>Authentication succeeded ! You can go to the next step.</p>'''))
551
        else:
552
            htmltext(_('''\
553
<p>Authentication has failed. To resolve this problem, you can :</p>
554
<ul>
555
<li><a href="send_authentication_request">Try authentication again</a></li>
556
<li><a href="see_authentication_response">See the response of the authentication request from the site</a></li>
557
<li><a href="modify_authentication_request">Modify the parameters of the authentication request</a></li>
558
<li><a href="authentication_success_criteria">Change the way Larpe detects the authentication is successful or not</a></li>
559
<li>Go back and change your username and/or password</li>
560
</ul>
561
'''))
562

  
563
        form.render()
564

  
565
    def see_authentication_response [html] (self):
566
        html_top('see_authentication_response', title=_('Authentication response'))
567
        '<h2>%s</h2>' % _('Authentication response')
568

  
569
        color = str('black')
570
        name = str(_('HTTP status code'))
571
        '<div style="margin-bottom: 0.1em; font-weight: bold; color: %s;">%s</div>' % (color, name)
572
        '<div style="margin-left: 1em; margin-bottom: 0.3em; color: %s;">%s (%s)</div>' % \
573
                    (color, self.host.auth_request_status, status_reasons[self.host.auth_request_status])
574

  
575
        '<dl>'
576
        '<dt><a href="see_response_html_page">%s</a></dt>' % _('See HTML page')
577
        '</dl>'
578
        '<div class="buttons"><a href="check_authentication"><input type="button" value="%s" /></a></div><br />' % _('Back')
579

  
580
    def see_response_html_page (self):
581
        return self.host.auth_request_data
582

  
583
    def authentication_success_criteria [html] (self):
584
        form = self.form_authentication_success_criteria()
585

  
586
        if form.get_widget('cancel').parse():
587
            return redirect('check_authentication')
588

  
589
        if form.is_submitted() and not form.has_errors():
590
            self.submit_authentication_success_criteria_form(form)
591
            return redirect('check_authentication')
592

  
593
        html_top('authentication_success_criteria', title=_('Criteria of authentication success'))
594
        '<h2>%s</h2>' % _('Criteria of authentication success')
595
        form.render()
596

  
597
    def form_authentication_success_criteria(self):
598
        form = Form(enctype='multipart/form-data')
599
        form.add(RadiobuttonsWidget, 'auth_system', title = _('Authentication system of the original site'),
600
                options=[
601
                    ('password', _('Check the existence of a password field'), 'password'),
602
                    ('match_text', _('Match some text to detect an authentication failure'), 'match_text'),
603
                ],
604
                sort=False,
605
                delim=htmltext('<br />'),
606
                value = self.host.auth_system)
607
        form.add(RegexStringWidget, 'auth_match_text', title = _('Text to match in case of authentication failure'),
608
                required = False, size = 50, value = self.host.auth_match_text)
609
        form.add_submit('submit', _('Submit'))
610
        form.add_submit('cancel', _('Cancel'))
611
        return form
612

  
613
    def submit_authentication_success_criteria_form(self, form):
614
        for f in ('auth_system', 'auth_match_text'):
615
            value = form.get_widget(f).parse()
616
            setattr(self.host, f, value)
617

  
618
        self.host.store()
619

  
620
    def modify_authentication_request [html] (self):
621
        html_top('modify_authentication_request', title=_('Modify the parameters of the authentication request'))
622
        '<h2>%s</h2>' % _('Modify the parameters of the authentication request')
623

  
624
        '<dl>'
625
        '<dt><a href="auth_request_post_parameters">%s</a></dt> <dd>%s</dd>' % (
626
                _('Modify POST parameters'), _('Configure the form attributes that will be sent within the authentication POST requests'))
627
        '<dt><a href="auth_request_http_headers">%s</a></dt> <dd>%s</dd>' % (
628
                _('Modify HTTP headers'), _('Configure the HTTP headers of the authentication requests made by Larpe'))
629
        '</dl>'
630
        '<div class="buttons"><a href="check_authentication"><input type="button" value="%s" /></a></div><br />' % _('Back')
631

  
632
    def auth_request_post_parameters [html] (self):
633
        form = self.form_auth_request_post_parameters()
634

  
635
        if form.get_widget('cancel').parse():
636
            return redirect('modify_authentication_request')
637

  
638
        if form.is_submitted() and not form.has_errors():
639
            self.submit_auth_request_post_parameters_form(form)
640
            return redirect('modify_authentication_request')
641

  
642
        html_top('auth_request_post_parameters', title=_('Configure POST parameters'))
643
        '<h2>%s</h2>' % _('Configure POST parameters')
644

  
645
        '<p>'
646
        htmltext(_('''Here are the detected form fields that will be sent as parameters of the authentication POST
647
 request. You can desactivate some or all of them, or change their value.'''))
648
        '</p>'
649

  
650
        form.render()
651

  
652
    def form_auth_request_post_parameters(self):
653
        form = Form(enctype='multipart/form-data')
654
        for name, value in self.host.post_parameters.iteritems():
655
            if value['immutable']:
656
                form.add(DictWidget, name, value, disabled = 'disabled')
657
            else:
658
                form.add(DictWidget, name, value)
659
        form.add_submit('submit', _('Submit'))
660
        form.add_submit('cancel', _('Cancel'))
661
        return form
662

  
663
    def submit_auth_request_post_parameters_form(self, form):
664
        for name, old_value in self.host.post_parameters.iteritems():
665
            value = form.get_widget(name).parse()
666
            if value['enabled'] == 'on':
667
                old_value['enabled'] = True
668
            else:
669
                old_value['enabled'] = False
670
            if old_value['immutable'] is False:
671
                old_value['value'] = value['value']
672
            self.host.post_parameters[name] = old_value
673
        self.host.store()
674

  
675
    def auth_request_http_headers [html] (self):
676
        form = self.form_auth_request_http_headers()
677

  
678
        if form.get_widget('cancel').parse():
679
            return redirect('modify_authentication_request')
680

  
681
        if form.is_submitted() and not form.has_errors():
682
            self.submit_auth_request_http_headers_form(form)
683
            return redirect('modify_authentication_request')
684

  
685
        html_top('auth_request_http_headers', title=_('Configure HTTP headers'))
686
        '<h2>%s</h2>' % _('Configure HTTP headers')
687

  
688
        '<p>'
689
        htmltext(_('''Here are the HTTP headers that will be sent within the authentication POST
690
 request. You can desactivate some or all of them, or change their value.'''))
691
        '</p>'
692

  
693
        form.render()
694

  
695
    def form_auth_request_http_headers(self):
696
        form = Form(enctype='multipart/form-data')
697
        for name, value in self.host.http_headers.iteritems():
698
            if value['immutable']:
699
                form.add(DictWidget, name, value, disabled = 'disabled')
700
            else:
701
                form.add(DictWidget, name, value)
702
        form.add(HtmlWidget, htmltext('<p>%s</p>' % \
703
            _('The headers "Host", "Accept-Encoding" and "Content-Length" will also automatically be sent.')))
704
        if get_cfg('use_proxy') and self.host.use_proxy:
705
            form.add(HtmlWidget, htmltext('<p>%s</p>' % \
706
                _('As Larpe uses a proxy for this site, the headers "Proxy-Authorization", "Proxy-Connection" and "Keep-Alive" will be sent as well.')))
707
        form.add_submit('submit', _('Submit'))
708
        form.add_submit('cancel', _('Cancel'))
709
        return form
710

  
711
    def submit_auth_request_http_headers_form(self, form):
712
        for name, old_value in self.host.http_headers.iteritems():
713
            value = form.get_widget(name).parse()
714
            if value['enabled'] == 'on':
715
                old_value['enabled'] = True
716
            else:
717
                old_value['enabled'] = False
718
            if old_value['immutable'] is False:
719
                old_value['value'] = value['value']
720
            self.host.http_headers[name] = old_value
721
        self.host.store()
722

  
723
    def generate_apache_filter (self):
724
        # Set Python filter path for Apache configuration
725
        if not hasattr(self.host, 'apache_python_paths'):
726
            self.host.apache_python_paths = []
727
        python_path = os.path.join(self.host.site_dir, 'filters')
728
        if python_path not in self.host.apache_python_paths:
729
            self.host.apache_python_paths.append(python_path)
730

  
731
        # Write Python filter
732
        python_file = open(os.path.join(self.host.site_dir, 'filters', 'output_replace_form.py'), 'w')
733
        python_file.write(open(os.path.join(get_publisher().data_dir, 'output_filter_base.py'), 'r').read())
734
        python_file.write('''\
735
def filter_page(filter, page):
736
    current_form = re.compile('<form .*?action="%(auth_form_action)s".*?>.*?</form>', re.DOTALL)
737
    return current_form.sub('<form method="post" action="/liberty/%(name)s/login"><input type="submit" value="Connexion" /></form>', page)
738
''' % { 'auth_form_action': self.host.auth_form_action, 'name': self.host.name })
739
        python_file.close()
740

  
741
        # Set Python filter for Apache configuration
742
        if not hasattr(self.host, 'apache_output_python_filters'):
743
            self.host.apache_output_python_filters = []
744
        if not 'output_replace_form' in self.host.apache_output_python_filters:
745
            self.host.apache_output_python_filters.append('output_replace_form')
746

  
747
    def sso_init_link [html] (self):
748
        form = self.form_sso_init_link()
749

  
750
        if form.get_widget('cancel').parse():
751
            return redirect('check_authentication')
752

  
753
        if form.is_submitted() and not form.has_errors():
754
            self.submit_sso_init_link_form(form)
755
            return redirect('metadatas')
756

  
757
        html_top('step7', title=_('Step 7 - Configure how a Single Sign On can be initiated'))
758
        '<h2>%s</h2>' % _('Step 7 - Configure how a Single Sign On can be initiated')
759

  
760
        '<p>'
761
        htmltext(_('''\
762
Most sites use one of the following 2 ways to allow users to initialise an authentication :
763
<ol>
764
<li>The site has a single authentication page. It redirects users to this page when they click a "Login" button or try to access a page which require users to be authenticated.</li>
765
<li>The site includes an authentication form in most or all of his pages. Users can authenticate on any of these pages, and don't need to be redirected to a separate authentication page.</li>
766
</ol>'''))
767
        '</p>'
768

  
769
        '<p>'
770
        htmltext(_('''\
771
Larpe needs to change this part of the site in a different way depending on the usual way the site works. It can either :
772
<ol>
773
<li>Redirect the user to the Single Sign On url instead of the previous authentication page.</li>
774
<li>Replace the form included in pages with a simple button. When users press this button, they will be redirected to the Single Sign On url.</li>
775
</ol>'''))
776
        '</p>'
777

  
778
        form.render()
779

  
780
    def form_sso_init_link(self):
781
        form = Form(enctype='multipart/form-data')
782
        form.add(RadiobuttonsWidget, 'auth_form_places', title = _('Authentication page or form'),
783
                options=[
784
                    ('form_once', _('The site has a single authentication page'), 'form_once'),
785
                    ('form_everywhere', _('The site includes an authentication form in most or all pages'), 'form_everywhere'),
786
                ],
787
                sort=False, required = True, delim=htmltext('<br />'), value = self.host.auth_form_places)
788
        form.add_submit('cancel', _('Previous'))
789
        form.add_submit('submit', _('Next'))
790
        return form
791

  
792
    def submit_sso_init_link_form(self, form):
793
        fields = [ 'auth_form_places', ]
794
        for f in fields:
795
            setattr(self.host, f, form.get_widget(f).parse())
796
        self.host.auth_form_url = self.host.auth_url
797

  
798
        if self.host.auth_form_places == 'form_everywhere' and self.host.auth_form_action:
799
            self.generate_apache_filter()
800
        else:
801
            if hasattr(self.host, 'apache_output_python_filters'):
802
                del self.host.apache_output_python_filters
803

  
804
        # Add mod proxy html
805
        # TODO: add an option somewhere to disable it
806
        if not hasattr(self.host, 'apache_output_filters'):
807
            self.host.apache_output_filters = []
808
        if 'proxy-html' not in self.host.apache_output_filters:
809
            self.host.apache_output_filters.append('proxy-html')
810

  
811
        self.host.store()
812
        write_apache2_vhosts()
813

  
814
    def metadatas [html] (self):
815
        form = Form(enctype='multipart/form-data')
816
        form.add_submit('cancel', _('Previous'))
817
        form.add_submit('submit', _('Next'))
818

  
819
        if form.get_widget('cancel').parse():
820
            return redirect('sso_init_link')
821

  
822
        if form.is_submitted():
823
            return redirect('check_full_configuration')
824

  
825
        html_top('step8', title=_('Step 8 - Configure the site metadatas on your identity provider'))
826
        '<h2>%s</h2>' % _('Step 8 - Configure the site metadatas on your identity provider')
827

  
828
        '<p>'
829
        htmltext(_('''Download the metadatas and the public key for this site and
830
upload them on your identity provider in order to use Liberty Alliance features'''))
831
        '</p>'
832

  
833
        '<dl>'
834
        if hasattr(self.host, str('base_url')):
835
            if lasso.SAML2_SUPPORT:
836
                saml2_metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
837
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
838
                            saml2_metadata_url,
839
                            _('Service Provider SAML 2.0 Metadata'),
840
                            _('Download Service Provider SAML 2.0 Metadata file'))
841
            metadata_url = '%s/metadata.xml' % self.host.base_url
842
            '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
843
                        metadata_url,
844
                        _('Service Provider Metadata'),
845
                        _('Download Service Provider ID-FF 1.2 Metadata file'))
846
        else:
847
            '<p>%s</p>' % _('No metadata has been generated for this host.')
848

  
849
        if hasattr(self.host, str('base_url')) and self.host.public_key and os.path.exists(self.host.public_key):
850
            public_key_url = '%s/public_key' % self.host.base_url
851
            '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
852
                        public_key_url,
853
                        _('Public key'),
854
                        _('Download Service Provider SSL Public Key file'))
855
        else:
856
            '<p>%s</p>' % _('No public key has been generated for this host.')
857
        '</dl>'
858

  
859
        form.render()
860

  
861
    def check_full_configuration [html] (self):
862
        form = Form(enctype='multipart/form-data')
863
        form.add_submit('cancel', _('Previous'))
864
        form.add_submit('submit', _('Finish'))
865

  
866
        if form.get_widget('cancel').parse():
867
            return redirect('metadatas')
868

  
869
        if form.is_submitted():
870
            return redirect('../..')
871

  
872
        html_top('step9', title=_('Step 9 - Check everything works'))
873
        '<h2>%s</h2>' % _('Step 9 - Check everything works')
874

  
875
        '<p>'
876
        htmltext(_('''Now you can fully test your site, start from the home page, initiate a Single Sign On,
877
federate your identities and do a Single Logout.'''))
878
        '</p>'
879

  
880
        '<p>'
881
        htmltext(_('The address of your site is : '))
882
        '<a href="%s">%s</a>' % (self.host.new_url, self.host.new_url)
883
        '</p>'
884

  
885
        '<p>'
886
        htmltext(_('''If everything works, click the "%(finish)s" button, otherwise you can go back and
887
check your settings or <a href=advanced_options>configure some advanced options</a>.''')) % { 'finish': _('Finish') }
888
        '</p>'
889

  
890
        form.render()
891

  
892
    def advanced_options [html] (self):
893
        form = self.form_advanced_options()
894

  
895
        if form.get_widget('cancel').parse():
896
            return redirect('check_full_configuration')
897

  
898
        if not form.is_submitted() or form.has_errors():
899
            get_response().breadcrumb.append( ('advanced_options', _('Advanced options')) )
900
            html_top('hosts', title = _('Advanced options'))
901
            '<h2>%s</h2>' % _('Advanced options')
902
            form.render()
903
        else:
904
            self.submit_advanced_options_form(form)
905
            return redirect('check_full_configuration')
906

  
907
    def form_advanced_options(self):
908
        form = Form(enctype='multipart/form-data')
909
        form.add(UrlOrAbsPathWidget, 'initiate_sso_url', title = _('URL which must initiate the SSO'),
910
                hint = _('''Address which must initiate the SSO. If empty, defaults to the previously
911
specified "%s"''') % _('Authentication form page address'),
912
                required = False, size = 50, value = self.host.initiate_sso_url)
913
        form.add(CheckboxWidget, 'redirect_root_to_login',
914
                title=_('Redirect the root URL of the site to the login page.'),
915
                value = self.host.redirect_root_to_login)
916
        form.add(UrlOrAbsPathWidget, 'return_url', title = _('Return address'),
917
                hint = _('Where the user will be redirected after a successful authentication'),
918
                required = False, size = 50, value = self.host.return_url)
919
        form.add(UrlOrAbsPathWidget, 'root_url', title = _('Error address'),
920
                hint = _('Where the user will be redirected after a disconnection or an error'),
921
                required = False, size = 50, value = self.host.root_url)
922
        form.add_submit('submit', _('Submit'))
923
        form.add_submit('cancel', _('Cancel'))
924
        return form
925

  
926
    def submit_advanced_options_form(self, form):
927
        old_redirect_root_to_login = self.host.redirect_root_to_login
928

  
929
        for f in ('initiate_sso_url', 'redirect_root_to_login', 'return_url', 'root_url'):
930
            value = form.get_widget(f).parse()
931
            setattr(self.host, f, value)
932

  
933
        self.host.store()
934

  
935
        if self.host.initiate_sso_url or self.host.redirect_root_to_login is not old_redirect_root_to_login:
936
            write_apache2_vhosts()
937

  
938
    def auto_detect_configuration(self):
939
#        """Guess other SP parameters"""
940
#        if self.host.auth_url is not None:
941
#            # Separate auth page
942
#            self.host.auth_form_url = self.host.auth_url
943
#        else:
944
#            if self.host.auth_form_page_url is not None:
945
#                # Auth form is not on index page
946
#                self.host.auth_form_url = self.host.auth_form_page_url
947
#            else:
948
#                # Auth form is on index page
949
#                self.host.auth_form_url = self.host.orig_site
950

  
951
        # Reset previous detected values
952
        self.host.auth_form = None
953
        self.host.auth_check_url = None
954
        self.host.login_field_name = None
955
        self.host.password_field_name = None
956
        if not self.host.post_parameters:
957
            self.host.post_parameters = {}
958

  
959
        self.parse_page(self.host.auth_form_url)
960

  
961
    def parse_page(self, page_url):
962
        # Get the authentication page
963
        try:
964
            response, status, page, auth_header = http_get_page(page_url, use_proxy=self.host.use_proxy)
965
        except Exception, msg:
966
            print msg
967
            return
968

  
969
        # Check if this site uses HTTP authentication
970
#        if status == 401:
971
#            if auth_header.startswith('Basic'):
972
#                self.host.auth_mode = 'http_basic'
973
#            else:
974
#                self.host.auth_mode = 'unsupported'
975
#            self.host.store()
976
#            return
977

  
978
        if page is None:
979
            return
980
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to get page'), self.host.auth_form_url))
981

  
982
        # Default authentication mode
983
        self.host.auth_mode = 'form'
984

  
985
        self.host.site_authentication_plugin = site_authentication.guess_site_authentication_class(page)
986
        self.parse_frames(page)
987
        self.parse_forms(page)
988
        if self.host.auth_form is not None:
989
            self.parse_form_action()
990
            input_fields = self.parse_input_fields()
991
            self.parse_login_field(input_fields)
992
            self.parse_password_field(input_fields)
993
            self.parse_select_fields(input_fields)
994
            self.parse_other_fields(input_fields)
995

  
996
    def parse_frames(self, page):
997
        '''If there are frames, parse them recursively'''
998
        regexp = re.compile("""<frame.*?src=["'](.*?)["'][^>]*?>""", re.DOTALL | re.IGNORECASE)
999
        found_frames = regexp.findall(page)
1000
        if found_frames:
1001
            for frame_url in found_frames:
1002
                if frame_url.startswith('http'):
1003
                    frame_full_url = frame_url
1004
                else:
1005
                    page_url_tokens = page_url.split('/')
1006
                    page_url_tokens[-1] = frame_url
1007
                    frame_full_url = '/'.join(page_url_tokens)
1008
                self.parse_page(frame_full_url)
1009

  
1010
    def parse_forms(self, page):
1011
        '''Search for an authentication form'''
1012
        # Get all forms
1013
        regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
1014
        found_forms = regexp.findall(page)
1015
        if not found_forms:
1016
            return
1017
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to find any form'), self.host.auth_form_url))
1018

  
1019
        # Get the first form with a password field
1020
        for found_form in found_forms:
1021
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1022
            if regexp.search(found_form) is not None:
1023
                self.host.auth_form = found_form
1024
                break
1025

  
1026
    def parse_form_action(self):
1027
        '''Get the action url of the form'''
1028
        regexp = re.compile("""<form.*?action=["']?(.*?)["']?[\s>].*?>""", re.DOTALL | re.IGNORECASE)
1029
        self.host.auth_form_action = regexp.findall(self.host.auth_form)[0]
1030
        # FIXME: Find a Python module which unescapes html entities
1031
        self.host.auth_check_url = self.host.auth_form_action.replace('&amp;', '&')
1032
        if not self.host.auth_check_url.startswith('http'):
1033
            if self.host.auth_check_url.startswith('/'):
1034
                if self.host.orig_site.startswith('https'):
1035
                    orig_site_root = 'https://%s' % urllib.splithost(self.host.orig_site[6:])[0]
1036
                else:
1037
                    orig_site_root = 'http://%s' % urllib.splithost(self.host.orig_site[5:])[0]
1038
                self.host.auth_check_url = orig_site_root + self.host.auth_check_url
1039
            else:
1040
                auth_form_url_tokens = self.host.auth_form_url.split('/')
1041
                auth_form_url_tokens[-1] = self.host.auth_check_url
1042
                self.host.auth_check_url = '/'.join(auth_form_url_tokens)
1043

  
1044
    def parse_input_fields(self):
1045
        '''Get all input fields'''
1046
        regexp = re.compile("""<input[^>]*?>""", re.DOTALL | re.IGNORECASE)
1047
        return regexp.findall(self.host.auth_form)
1048

  
1049
    def parse_login_field(self, input_fields):
1050
        '''Get login field name'''
1051
        try:
1052
            regexp = re.compile("""<input[^>]*?type=["']?text["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1053
            text_fields = regexp.findall(self.host.auth_form)
1054
            login_field = ''
1055
            if text_fields:
1056
                login_field = text_fields[0]
1057
            else:
1058
                for field in input_fields:
1059
                    if re.search("""type=["']?""", field, re.DOTALL | re.IGNORECASE) is None:
1060
                        login_field = field
1061
                        break
1062
            regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1063
            self.host.login_field_name = regexp.findall(login_field)[0]
1064
            if not self.host.post_parameters.has_key(self.host.login_field_name):
1065
                self.host.post_parameters[self.host.login_field_name] = \
1066
                    { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1067
                self.host.store()
1068
        except IndexError, e:
1069
            self.host.login_field_name = None
1070
            print 'Error handling login field : %s' % e
1071

  
1072
    def parse_password_field(self, input_fields):
1073
        '''Get password field name'''
1074
        try:
1075
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1076
            password_field = regexp.findall(self.host.auth_form)[0]
1077
            regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1078
            self.host.password_field_name = regexp.findall(password_field)[0]
1079
            if not self.host.post_parameters.has_key(self.host.password_field_name):
1080
                self.host.post_parameters[self.host.password_field_name] = \
1081
                    { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1082
        except IndexError, e:
1083
            self.host.password_field_name = None
1084
            print 'Error handling password field : %s' % e
1085

  
1086
    def parse_select_fields(self, input_fields):
1087
        '''Add select fields to host attributes'''
1088
        # First added for Imuse (Rennes)
1089
        regexp = re.compile("""<select.*?</select>""", re.DOTALL | re.IGNORECASE)
1090
        self.host.select_fields = {}
1091
        for field in regexp.findall(self.host.auth_form):
1092
            try:
1093
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1094
                name = regexp.findall(field)[0]
1095
                regexp = re.compile("""<option[^>]*?>.*?</option>""", re.DOTALL | re.IGNORECASE)
1096
                options = regexp.findall(field)
1097
                values = []
1098
                for option in options:
1099
                    regexp = re.compile("""<option[^>]*?>(.*?)</option>""", re.DOTALL | re.IGNORECASE)
1100
                    option_label = regexp.findall(option)
1101
                    regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1102
                    option_value = regexp.findall(option)
1103
                    if option_label:
1104
                        if not option_value:
1105
                            option_value = option_label
1106
                        values.append((option_value[0], option_label[0]))
1107
                    else:
1108
                        print >> sys.stderr, 'W: Could not parse select options'
1109
                self.host.select_fields[name] = values
1110
                if not self.host.post_parameters.has_key(name):
1111
                    self.host.post_parameters[name] = \
1112
                        { 'enabled': True, 'value': _('(filled by users)'), 'immutable': True }
1113
            except IndexError, e:
1114
                continue
1115

  
1116
    def parse_other_fields(self, input_fields):
1117
        '''Get the default value of all other fields'''
1118
        self.host.other_fields = {}
1119

  
1120
        # Get hidden fields
1121
        regexp = re.compile("""<input[^>]*?type=["']?hidden["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1122
        other_fields = regexp.findall(self.host.auth_form)
1123

  
1124
        # Only get first submit field
1125
        regexp = re.compile("""<input[^>]*?type=["']?submit["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1126
        found = regexp.findall(self.host.auth_form)
1127
        if found:
1128
            if other_fields:
1129
                other_fields.append(found[0])
1130
            else:
1131
                other_fields = found[0]
1132

  
1133
        for field in other_fields:
1134
            try:
1135
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1136
                name = regexp.findall(field)[0]
1137
                regexp = re.compile("""value=["'](.*?)["'][\s/>]""", re.DOTALL | re.IGNORECASE)
1138
                value = regexp.findall(field)[0]
1139
                self.host.other_fields[name] = value
1140
                if not self.host.post_parameters.has_key(name):
1141
                    self.host.post_parameters[name] = { 'enabled': True, 'value': value, 'immutable': False }
1142
            except IndexError, e:
1143
                continue
1144

  
1145

  
1146
class HostUI:
1147
    def __init__(self, host):
1148
        self.host = host
1149

  
1150
    def form_edit(self):
1151
        # FIXME : homogeneise the size of the fields
1152
        form = Form(enctype='multipart/form-data')
1153
        form.add(StringWidget, 'label', title = _('Site name'), required = True,
1154
                size = 30, value = self.host.label)
1155
        form.add(UrlWidget, 'orig_site', title = _('Original site root address'), required = True,
1156
                size = 50, value = self.host.orig_site)
1157
        form.add(ValidUrlWidget, 'auth_url', title = _('Authentication page'),
1158
                hint = _('If there is a separate authentication page'),
1159
                required = False, size = 70, value = self.host.auth_url)
1160
        form.add(ValidUrlWidget, 'auth_form_page_url',
1161
                title = _('Authentication form page'),
1162
                hint = _('If the authentication form is not in a separate page and not in the index page either'),
1163
                required = False, size = 70, value = self.host.auth_form_page_url)
1164
        form.add(ValidUrlWidget, 'logout_url', title = _('Logout address'), required = False,
1165
                size = 70, value = self.host.logout_url)
1166
        form.add(StringWidget, 'reversed_hostname', title = _('Reversed host name'),
1167
                size = 30, required = True, value = self.host.reversed_hostname)
1168
        form.add(StringWidget, 'reversed_directory', title = _('Reversed directory'),
1169
                size = 30, required = False, value = self.host.reversed_directory)
1170
        form.add(CheckboxWidget, 'use_ssl', title = _('Use SSL'),
1171
                hint = _('This only affects the connection between the browser and Larpe'),
1172
                value = self.host.use_ssl)
1173
        form.add_submit('submit', _('Submit'))
1174
        form.add_submit('cancel', _('Cancel'))
1175
        return form
1176

  
1177
    def submit_edit_form(self, form):
1178
        metadata_cfg = {}
1179
        for f in ('label', 'orig_site', 'auth_url', 'auth_form_page_url', 'logout_url', 'reversed_hostname',
1180
                    'reversed_directory', 'use_ssl'):
1181
            widget = form.get_widget(f)
1182
            setattr(self.host, f, widget.parse())
1183
            # Get the special use_proxy attribute if it exists
1184
            if hasattr(widget, 'use_proxy'):
1185
                self.host.use_proxy = widget.use_proxy
1186

  
1187
        self.host.organization_name = self.host.label
1188
        metadata_cfg['organization_name'] = self.host.organization_name
1189

  
1190
        # Build host name from host label
1191
        self.host.name = self.host.label.lower()
1192
        invalid_characters = [' ', "'"]
1193
        for char in invalid_characters:
1194
            self.host.name = self.host.name.replace(char, '_')
1195

  
1196
        # Set url scheme (ie protocol) according to SSL usage
1197
        if self.host.use_ssl:
1198
            self.host.scheme = 'https'
1199
        else:
1200
            self.host.scheme = 'http'
1201

  
1202
        # Liberty Alliance / SAML parameters
1203
        base_url = '%s://%s%s/liberty/%s/liberty' % (self.host.scheme,
1204
                                                     self.host.reversed_hostname,
1205
                                                     get_request().environ['SCRIPT_NAME'],
1206
                                                     self.host.name)
1207
        metadata_cfg['base_url'] = base_url
1208
        self.host.base_url = base_url
1209

  
1210
        if lasso.SAML2_SUPPORT:
1211
            saml2_base_url = '%s://%s%s/liberty/%s/saml' % (self.host.scheme,
1212
                                                         self.host.reversed_hostname,
1213
                                                         get_request().environ['SCRIPT_NAME'],
1214
                                                         self.host.name)
1215
            metadata_cfg['saml2_base_url'] = saml2_base_url
1216
            self.host.saml2_base_url = saml2_base_url
1217

  
1218
        provider_id = '%s/metadata' % base_url
1219
        metadata_cfg['provider_id'] = provider_id
1220
        self.host.provider_id = provider_id
1221

  
1222
        if lasso.SAML2_SUPPORT:
1223
            saml2_provider_id = '%s/metadata' % saml2_base_url
1224
            metadata_cfg['saml2_provider_id'] = saml2_provider_id
1225
            self.host.saml2_provider_id = saml2_provider_id
1226

  
1227
        # Storage directories
1228
        if self.host.reversed_directory is None:
1229
            reversed_dir = 'default'
1230
        else:
1231
            reversed_dir = self.host.reversed_directory
1232
        site_dir = os.path.join(get_publisher().app_dir, 'sp',
1233
                    self.host.reversed_hostname, reversed_dir)
1234
        user_dir = os.path.join(site_dir, 'users')
1235
        token_dir = os.path.join(site_dir, 'tokens')
1236
        for dir in (site_dir, user_dir, token_dir):
1237
            if not os.path.isdir(dir):
1238
                os.makedirs(dir)
1239
        metadata_cfg['site_dir'] = site_dir
1240
        self.host.site_dir = site_dir
1241

  
1242
        # Tweaking for larpe vhosts
1243
        hostname_dir = get_abs_path(os.path.join('..', self.host.reversed_hostname))
1244
        if not os.path.exists(hostname_dir):
1245
            os.mkdir(hostname_dir)
1246
        # Load the configuration from the main directory
1247
        get_publisher().reload_cfg()
1248
        get_publisher().write_cfg(hostname_dir)
1249

  
1250
        # Generate SSL keys
1251
        private_key_path = os.path.join(site_dir, 'private_key.pem')
1252
        public_key_path = os.path.join(site_dir, 'public_key.pem')
1253
        if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
1254
            set_provider_keys(private_key_path, public_key_path)
1255
            self.host.private_key = private_key_path
1256
            self.host.public_key = public_key_path
1257

  
1258
        # Read public key
1259
        public_key = ''
1260
        if self.host.public_key is not None and os.path.exists(self.host.public_key):
1261
            metadata_cfg['signing_public_key'] = open(self.host.public_key).read()
1262

  
1263
        # Write metadatas
1264
        metadata_path = os.path.join(site_dir, 'metadata.xml')
1265
        open(metadata_path, 'w').write(get_metadata(metadata_cfg))
1266
        self.host.metadata = metadata_path
1267

  
1268
        if lasso.SAML2_SUPPORT:
1269
            saml2_metadata_path = os.path.join(site_dir, 'saml2_metadata.xml')
1270
            open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
1271
            self.host.saml2_metadata = saml2_metadata_path
1272

  
1273
        # Use default idps
1274
#        idp_dir = os.path.join(get_publisher().app_dir, 'idp')
1275
#        self.host.idps = os.listdir(idp_dir)
1276

  
1277
        self.host.store()
1278

  
1279
        for attr in ('auth_check_url', 'login_field_name', 'password_field_name'):
1280
            if not hasattr(self.host, attr) or not getattr(self.host, attr):
1281
                self.auto_detect_configuration()
1282
                break
1283

  
1284
        write_apache2_vhosts()
1285

  
1286
    def auto_detect_configuration(self):
1287
        """Guess other SP parameters"""
1288
        if self.host.auth_url is not None:
1289
            # Separate auth page
1290
            self.host.auth_form_url = self.host.auth_url
1291
        else:
1292
            if self.host.auth_form_page_url is not None:
1293
                # Auth form is not on index page
1294
                self.host.auth_form_url = self.host.auth_form_page_url
1295
            else:
1296
                # Auth form is on index page
1297
                self.host.auth_form_url = self.host.orig_site
1298

  
1299
        # Reset previous detected values
1300
        self.host.auth_form = None
1301
        self.host.auth_check_url = None
1302
        self.host.login_field_name = None
1303
        self.host.password_field_name = None
1304
        self.host.store()
1305

  
1306
        self.parse_page(self.host.auth_form_url)
1307

  
1308
    def parse_page(self, page_url):
1309
        # Get the authentication page
1310
        try:
1311
            response, status, page, auth_header = http_get_page(page_url, use_proxy=self.host.use_proxy)
1312
        except Exception, msg:
1313
            print msg
1314
            return
1315

  
1316
        # Check if this site uses HTTP authentication
1317
        if status == 401:
1318
            if auth_header.startswith('Basic'):
1319
                self.host.auth_mode = 'http_basic'
1320
            else:
1321
                self.host.auth_mode = 'unsupported'
1322
            self.host.store()
1323
            return
1324

  
1325
        if page is None:
1326
            return
1327
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to get page'), self.host.auth_form_url))
1328

  
1329
        self.host.site_authentication_plugin = site_authentication.guess_site_authentication_class(page)
1330

  
1331
        # If there are frames, parse them recursively
1332
        regexp = re.compile("""<frame.*?src=["'](.*?)["'][^>]*?>""", re.DOTALL | re.IGNORECASE)
1333
        found_frames = regexp.findall(page)
1334
        if found_frames:
1335
            for frame_url in found_frames:
1336
                if frame_url.startswith('http'):
1337
                    frame_full_url = frame_url
1338
                else:
1339
                    page_url_tokens = page_url.split('/')
1340
                    page_url_tokens[-1] = frame_url
1341
                    frame_full_url = '/'.join(page_url_tokens)
1342
                self.parse_page(frame_full_url)
1343

  
1344
        # Default authentication mode
1345
        self.host.auth_mode = 'form'
1346

  
1347
        # Get all forms
1348
        regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
1349
        found_forms = regexp.findall(page)
1350
        if not found_forms:
1351
            return
1352
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to find any form'), self.host.auth_form_url))
1353

  
1354
        # Get the first form with a password field
1355
        found = False
1356
        for found_form in found_forms:
1357
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1358
            if regexp.search(found_form) is not None:
1359
                self.host.auth_form = found_form
1360
                found = True
1361
                break
1362

  
1363
        if not found:
1364
            return
1365
            #raise FormError, ('auth_check_url', _('Failed to find the authentication form'))
1366

  
1367
        # If we found a form, search for all needed information
1368

  
1369
        # Get the action url of the form
1370
        regexp = re.compile("""<form.*?action=["']?(.*?)["']?[\s>].*?>""", re.DOTALL | re.IGNORECASE)
1371
        self.host.auth_check_url = regexp.findall(self.host.auth_form)[0]
1372
        # FIXME : Find a module which unescapes html entities
1373
        self.host.auth_check_url = self.host.auth_check_url.replace('&amp;', '&')
1374
        if not self.host.auth_check_url.startswith('http'):
1375
            if self.host.auth_check_url.startswith('/'):
1376
                if self.host.orig_site.startswith('https'):
1377
                    orig_site_root = 'https://%s' % urllib.splithost(self.host.orig_site[6:])[0]
1378
                else:
1379
                    orig_site_root = 'http://%s' % urllib.splithost(self.host.orig_site[5:])[0]
1380
                self.host.auth_check_url = orig_site_root + self.host.auth_check_url
1381
            else:
1382
                auth_form_url_tokens = self.host.auth_form_url.split('/')
1383
                auth_form_url_tokens[-1] = self.host.auth_check_url
1384
                self.host.auth_check_url = '/'.join(auth_form_url_tokens)
1385
        
1386
        # Get all inputs
1387
        regexp = re.compile("""<input[^>]*?>""", re.DOTALL | re.IGNORECASE)
1388
        inputs = regexp.findall(self.host.auth_form)
1389

  
1390
        # Get login field name
1391
        try:
1392
            regexp = re.compile("""<input[^>]*?type=["']?text["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1393
            text_fields = regexp.findall(self.host.auth_form)
1394
            login_field = ''
1395
            if text_fields:
1396
                login_field = text_fields[0]
1397
            else:
1398
                for field in inputs:
1399
                    if re.search("""type=["']?""", field, re.DOTALL | re.IGNORECASE) is None:
1400
                        login_field = field
1401
                        break
1402
            regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1403
            self.host.login_field_name = regexp.findall(login_field)[0]
1404
        except IndexError, e:
1405
            self.host.login_field_name = None
1406
            print 'Error handling login field : %s' % e
1407

  
1408
        # Get password field name
1409
        try:
1410
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1411
            password_field = regexp.findall(self.host.auth_form)[0]
1412
            regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1413
            self.host.password_field_name = regexp.findall(password_field)[0]
1414
        except IndexError, e:
1415
            self.host.password_field_name = None
1416
            print 'Error handling password field : %s' % e
1417

  
1418
        # Get the default value of all other fields
1419
        self.host.other_fields = {}
1420

  
1421
        # Get hidden and submit fields
1422
        regexp = re.compile("""<input[^>]*?type=["']?(?:hidden|submit)["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
1423
        other_fields = regexp.findall(self.host.auth_form)
1424
        for field in other_fields:
1425
            try:
1426
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1427
                name = regexp.findall(field)[0]
1428
                regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1429
                value = regexp.findall(field)[0]
1430
                self.host.other_fields[name] = value
1431
            except IndexError, e:
1432
                continue
1433

  
1434
        # Add select fields to host attributes
1435
        # First added for Imuse (Rennes)
1436
        regexp = re.compile("""<select.*?</select>""", re.DOTALL | re.IGNORECASE)
1437
        self.host.select_fields = {}
1438
        for field in regexp.findall(page):
1439
            try:
1440
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1441
                name = regexp.findall(field)[0]
1442
                regexp = re.compile("""<option[^>]*?>.*?</option>""", re.DOTALL | re.IGNORECASE)
1443
                options = regexp.findall(field)
1444
                values = []
1445
                for option in options:
1446
                    regexp = re.compile("""value=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
1447
                    option_value = regexp.findall(option)
1448
                    regexp = re.compile("""<option[^>]*?>(.*?)</option>""", re.DOTALL | re.IGNORECASE)
1449
                    option_label = regexp.findall(option)
1450
                    if option_value and option_label:
1451
                        values.append((option_value[0], option_label[0]))
1452
                self.host.select_fields[name] = values
1453
            except IndexError, e:
1454
                continue
1455

  
1456
#            print 'action : %s' % self.host.auth_check_url
1457
#            print 'login field : %s' % self.host.login_field_name
1458
#            print 'password field : %s' % self.host.password_field_name
1459
#            print 'other fields : %s' % self.host.other_fields
1460

  
1461
        self.host.store()
1462

  
1463
    def form_auto_detected_configuration(self):
1464
        form = Form(enctype='multipart/form-data')
1465
        form.add(RadiobuttonsWidget, 'auth_mode', title = _('Authentication mode'),
1466
                options=[
1467
                    ('form', _('Form'), 'form'),
1468
                    ('http_basic', _('HTTP Basic'), 'http_basic'),
1469
                ],
1470
                sort=False,
1471
                required = True,
1472
                value = self.host.auth_mode)
1473
        form.add(ValidUrlWidget, 'auth_check_url', title = _('Address where the authentication form must be sent'),
1474
                required = False, size = 70, value = self.host.auth_check_url)
1475
        form.add(StringWidget, 'login_field_name', title = _('Name of the login field'),
1476
                required = False, size = 30, value = self.host.login_field_name)
1477
        form.add(StringWidget, 'password_field_name', title = _('Name of the password field'),
1478
                required = False, size = 30, value = self.host.password_field_name)
1479
        form.add_submit('submit', _('Submit'))
1480
        form.add_submit('cancel', _('Cancel'))
1481
        return form        
1482

  
1483
    def submit_auto_detected_configuration_form(self, form):
1484
        for f in ('auth_mode', 'auth_check_url', 'login_field_name', 'password_field_name'):
1485
            value = form.get_widget(f).parse()
1486
            setattr(self.host, f, value)
1487

  
1488
        # Ensure auth_check_url is a full url
1489
        if 'auth_mode' == 'form':
1490
            if not self.host.auth_check_url.startswith('http'):
1491
                if self.host.auth_check_url.startswith('/'):
1492
                    if self.host.orig_site.endswith('/'):
1493
                        self.host.auth_check_url = self.host.orig_site + self.host.auth_check_url[1:]
1494
                    else:
1495
                        self.host.auth_check_url = self.host.orig_site + self.host.auth_check_url
1496
                else:
1497
                    auth_form_url_tokens = self.host.auth_form_url.split('/')
1498
                    auth_form_url_tokens[-1] = self.host.auth_check_url
1499
                    self.host.auth_check_url = '/'.join(auth_form_url_tokens)
1500

  
1501
        self.host.store()
1502

  
1503
    def form_advanced_configuration(self):
1504
        form = Form(enctype='multipart/form-data')
1505
        form.add(UrlOrAbsPathWidget, 'return_url', title = _('Return address'),
1506
                hint = _('Where the user will be redirected after a successful authentication'),
1507
                required = False, size = 50, value = self.host.return_url)
1508
        form.add(UrlOrAbsPathWidget, 'root_url', title = _('Error address'),
1509
                hint = _('Where the user will be redirected after a disconnection or an error'),
1510
                required = False, size = 50, value = self.host.root_url)
1511
        form.add(CheckboxWidget, 'redirect_root_to_login',
1512
                title=_('Redirect the root url of the site to the login page.'),
1513
                value = self.host.redirect_root_to_login)
1514
        form.add(CheckboxWidget, 'send_hidden_fields', title=_('Send authentication form hidden fields'),
1515
                value = self.host.send_hidden_fields)
1516
        form.add(RadiobuttonsWidget, 'auth_system', title = _('Authentication system of the original site'),
1517
                options=[
1518
                    ('password', _('Check the existence of a password field'), 'password'),
1519
                    ('match_text', _('Match some text to detect an authentication failure'), 'match_text'),
1520
                ],
1521
                sort=False,
1522
                delim=htmltext('<br />'),
1523
                value = self.host.auth_system)
1524
        form.add(RegexStringWidget, 'auth_match_text', title = _('Text to match in case of authentication failure'),
1525
                required = False, size = 50, value = self.host.auth_match_text)
1526
        form.add_submit('submit', _('Submit'))
1527
        form.add_submit('cancel', _('Cancel'))
1528
        return form
1529

  
1530
    def submit_advanced_configuration_form(self, form):
1531
        old_redirect_root_to_login = self.host.redirect_root_to_login
1532

  
1533
        for f in ('return_url', 'root_url', 'redirect_root_to_login', 'send_hidden_fields', 'auth_system', 'auth_match_text'):
1534
            value = form.get_widget(f).parse()
1535
            setattr(self.host, f, value)
1536

  
1537
        self.host.store()
1538

  
1539
        if self.host.redirect_root_to_login is not old_redirect_root_to_login:
1540
            write_apache2_vhosts()
1541

  
1542
    def form_apache_filters(self):
1543
        form = Form(enctype='multipart/form-data')
1544
        if not hasattr(self.host, 'apache_output_filters'):
1545
            self.host.apache_output_filters = [ 'proxy-html' ]
1546
            self.host.store()
1547
        form.add(CheckboxWidget, 'proxy-html', title = _('HTML proxy'),
1548
                hint = _('Converts urls in the html pages according to the host new domain name'),
1549
                value = 'proxy-html' in self.host.apache_output_filters)
1550
        form.add_submit('submit', _('Submit'))
1551
        form.add_submit('cancel', _('Cancel'))
1552
        return form
1553

  
1554
    def submit_apache_filters_form(self, form):
1555
        f = 'proxy-html'
1556
        value = form.get_widget(f).parse()
1557
        if value is True and f not in self.host.apache_output_filters:
1558
            self.host.apache_output_filters.append(f)
1559
        if value is False and f in self.host.apache_output_filters:
1560
            self.host.apache_output_filters.remove(f)
1561
        self.host.store()
1562
        write_apache2_vhosts()
1563

  
1564
class HostPage(Directory):
1565
    _q_exports = ['', 'minimal_configuration', 'auto_detect_configuration', 'auto_detected_configuration', 'advanced_configuration', 'apache_filters', 'see_current_configuration', 'delete']
1566

  
1567
    def __init__(self, host_id):
1568
        self.host = Host.get(host_id)
1569
        self.host_ui = HostUI(self.host)
1570
        get_response().breadcrumb.append((host_id + '/', self.host.label))
1571

  
1572
    def _q_lookup(self, component):
1573
        if component == 'configuration_assistant':
1574
            return ConfigurationAssistant(self.host)
1575
        elif component == 'forms_prefill':
1576
            return FormsDirectory(self.host)
1577

  
1578
    def _q_index [html] (self):
1579
        get_publisher().reload_cfg()
1580
        html_top('hosts', title = self.host.label)
1581

  
1582
        '<h2>%s</h2>' % _('Reverse Proxy')
1583
        
1584
        '<dl>'
1585
        '<dt><a href="minimal_configuration">%s</a></dt> <dd>%s</dd>' % (
1586
                _('Minimal Configuration'), _('Configure the minimum parameters to set up a reverse proxy for this site'))
1587
        '<dt><a href="auto_detected_configuration">%s</a></dt> <dd>%s</dd>' % (
1588
                _('Auto Detected Configuration'), _('Check the auto detected parameters and change them if necessary'))                
1589
        '<dt><a href="advanced_configuration">%s</a></dt> <dd>%s</dd>' % (
1590
                _('Advanced Configuration'), _("Configure advanced parameters if it doesn't work with minimal configuration"))
1591
        '<dt><a href="apache_filters">%s</a></dt> <dd>%s</dd>' % (
1592
                _('Apache filters'), _('Select what apache filters to use'))
1593
        '<dt><a href="see_current_configuration">%s</a></dt> <dd>%s</dd>' % (
1594
                _('See Current Configuration'), _('See the current configuration of this host'))
1595
        '</dl>'
1596

  
1597
        if lasso.SAML2_SUPPORT:
1598
            '<h2>Liberty Alliance & SAML 2.0</h2>'
1599
        else:
1600
            '<h2>Liberty Alliance</h2>'
1601

  
1602
        '<dl>'
1603
        if hasattr(self.host, str('base_url')):
1604
            if lasso.SAML2_SUPPORT:
1605
                saml2_metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
1606
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
1607
                            saml2_metadata_url,
1608
                            _('Service Provider SAML 2.0 Metadata'),
1609
                            _('Download Service Provider SAML 2.0 Metadata file'))
1610
            metadata_url = '%s/metadata.xml' % self.host.base_url
1611
            '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
1612
                        metadata_url,
1613
                        _('Service Provider ID-FF 1.2 Metadata'),
1614
                        _('Download Service Provider ID-FF 1.2 Metadata file'))
1615
        else:
1616
            '<p>%s</p>' % _('No metadata has been generated for this host.')
1617

  
1618
        if hasattr(self.host, str('base_url')) and self.host.public_key and os.path.exists(self.host.public_key):
1619
            public_key_url = '%s/public_key' % self.host.base_url
1620
            '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
1621
                        public_key_url,
1622
                        _('Public key'),
1623
                        _('Download Service Provider SSL Public Key file'))
1624
        else:
1625
            '<p>%s</p>' % _('No public key has been generated for this host.')
1626
        '</dl>'
1627

  
1628
        '<h2>%s</h2>' % _('Form prefilling with ID-WSF')
1629
        
1630
        '<dl>'
1631
        '<dt><a href="forms_prefill/">%s</a></dt> <dd>%s</dd>' % (
1632
                _('Forms'), _('Configure the forms to prefill'))
1633
        '</dl>'
1634

  
1635
    def minimal_configuration [html] (self):
1636
        form = self.host_ui.form_edit()
1637
        if form.get_widget('cancel').parse():
1638
            return redirect('.')
1639

  
1640
        if form.is_submitted() and not form.has_errors():
1641
            check_minimal_configuration(form)
1642

  
1643
        if form.is_submitted() and not form.has_errors():
1644
            self.host_ui.submit_edit_form(form)
1645
            return redirect('see_current_configuration')
1646

  
1647
        get_response().breadcrumb.append( ('minimal_configuration', _('Edit')) )
1648
        html_top('hosts', title = _('Edit Host'))
1649
        '<h2>%s</h2>' % _('Edit Host')
1650

  
1651
        form.render()
1652

  
1653
    def auto_detect_configuration (self):
1654
        self.host_ui.auto_detect_configuration()
1655
        return redirect('auto_detected_configuration')
1656

  
1657
    def auto_detected_configuration [html] (self, detect=False):
1658
        form = self.host_ui.form_auto_detected_configuration()
1659
        if form.get_widget('cancel').parse():
1660
            return redirect('.')
1661

  
1662
        if not form.is_submitted() or form.has_errors():
1663
            get_response().breadcrumb.append( ('auto_detected_configuration', _('Auto Detected Configuration')) )
1664
            html_top('hosts', title = _('Auto Detected Configuration'))
1665
            '<h2>%s</h2>' % _('Auto Detected Configuration')
1666
            '<div><a href="auto_detect_configuration"><input type="button" value="%s" /></a> %s </div><br />' \
1667
                % (_('Auto detect'), _('Warning, this will erase your custom modifications !'))
1668
            form.render()
1669
        else:
1670
            self.host_ui.submit_auto_detected_configuration_form(form)
1671
            return redirect('see_current_configuration')
1672

  
1673
    def advanced_configuration [html] (self):
1674
        form = self.host_ui.form_advanced_configuration()
1675
        if form.get_widget('cancel').parse():
1676
            return redirect('.')
1677

  
1678
        if not form.is_submitted() or form.has_errors():
1679
            get_response().breadcrumb.append( ('advanced_configuration', _('Advanced Configuration')) )
1680
            html_top('hosts', title = _('Advanced Configuration'))
1681
            '<h2>%s</h2>' % _('Advanced Configuration')
1682
            form.render()
1683
        else:
1684
            self.host_ui.submit_advanced_configuration_form(form)
1685
            return redirect('see_current_configuration')
1686

  
1687
    def apache_filters [html] (self):
1688
        form = self.host_ui.form_apache_filters()
1689
        if form.get_widget('cancel').parse():
1690
            return redirect('.')
1691

  
1692
        if not form.is_submitted() or form.has_errors():
1693
            get_response().breadcrumb.append( ('apache_filters', _('Apache filters')) )
1694
            html_top('hosts', title = _('Apache filters'))
1695
            '<h2>%s</h2>' % _('Apache filters')
1696
            form.render()
1697
        else:
1698
            self.host_ui.submit_apache_filters_form(form)
1699
            return redirect('see_current_configuration')
1700

  
1701
    def see_current_configuration [html] (self):
1702
        get_response().breadcrumb.append( ('see_current_configuration', _('See Current Configuration')) )
1703
        html_top('hosts', title = _('Current Host Configuration'))
1704
        '<h2>%s</h2>' % _('Current Host Configuration')
1705

  
1706
        for attr in ('auth_check_url', 'login_field_name', 'password_field_name'):
1707
            if not getattr(self.host, str(attr)):
1708
                '<div class="errornotice">%s</div>' % _("This site is not fully configured yet. \
1709
The following red fields don't have a correct value.")
1710
                break
1711

  
1712
        # New url for this host
1713
        url = '%s://%s%s/' % (self.host.scheme, self.host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
1714
        if self.host.reversed_directory is not None:
1715
            url += '%s/' % self.host.reversed_directory
1716
        '<div style="margin-bottom: 0.1em; font-weight: bold;">%s</div>' % _('New url for this host')
1717
        '<div style="margin-left: 1em; margin-bottom: 0.3em;"><a href="%s">%s</a></div>' % (url, url)
1718

  
1719
        get_publisher().reload_cfg()
1720
        if get_cfg('use_proxy'):
1721
            '<div style="margin-bottom: 0.1em; font-weight: bold;">%s</div>' % _('Use proxy')
1722
            '<div style="margin-left: 1em; margin-bottom: 0.3em;">%s</div>' % self.host.use_proxy
1723

  
1724
        host_attrs_minimal = (
1725
            ('label', _('Site name')),
1726
            ('orig_site', _('Original site root address')),
1727
            ('auth_url', _('Authentication page')),
1728
            ('auth_form_page_url', _('Authentication form page')),
1729
            ('logout_url', _('Logout address')),
1730
            ('reversed_hostname', _('Reversed host name')),
1731
            ('reversed_directory', _('Reversed directory')),
1732
            ('use_ssl', _('Use SSL'))
1733
        )
1734
        host_attrs_auto = (
1735
            ('auth_mode', _('Authentication mode')),
1736
            ('auth_check_url', _('Address where the authentication form must be sent')),
1737
            ('login_field_name', _('Name of the login field')),
1738
            ('password_field_name', _('Name of the password field')),
1739
            ('site_authentication_plugin', _('Plugin used for site specific authentication behaviour'))
1740
        )
1741
        host_attrs_advanced = (
1742
            ('return_url', _('Return address')),
1743
            ('root_url', _('Root address')),
1744
            ('redirect_root_to_login', _('Redirect the root url of the site to the login page.')),
1745
            ('auth_system', _('Authentication system of the original site')),
1746
            ('auth_match_text', _('Text to match in case of authentication failure')),
1747
            ('send_hidden_fields', _('Send authentication form hidden fields'))
1748
        )
1749
        host_menus = (
1750
            (host_attrs_minimal, '<a href="minimal_configuration">%s</a>' % _('Minimal Configuration')),
1751
            (host_attrs_auto, '<a href="auto_detected_configuration">%s</a>' % _('Auto Detected Configuration')),
1752
            (host_attrs_advanced, '<a href="advanced_configuration">%s</a>' % _('Advanced Configuration')),
1753
        ) 
1754
        for host_attrs, category in host_menus:
1755
            '<h3>%s</h3>' % category
1756
            for attr, name in host_attrs:
1757
                color = 'black'
1758
                if attr in ('auth_check_url', 'login_field_name', 'password_field_name') and \
1759
                        not getattr(self.host, str(attr)):
1760
                    color = 'red'
1761
                '<div style="margin-bottom: 0.1em; font-weight: bold; color: %s;">%s</div>' % (color, name)
1762
                '<div style="margin-left: 1em; margin-bottom: 0.3em; color: %s;">%s</div>' % \
1763
                    (color, getattr(self.host, str(attr)))
1764
                if getattr(self.host, str(attr)) == '':
1765
                    '<br />'
1766
        '<div class="buttons"><a href="."><input type="button" value="%s" /></a></div><br />' % _('Back')
1767

  
1768
    def delete [html] (self):
1769
        form = Form(enctype='multipart/form-data')
1770
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
1771
                        'You are about to irrevocably delete this host.')))
1772
        form.add_submit('submit', _('Submit'))
1773
        form.add_submit('cancel', _('Cancel'))
1774
        if form.get_widget('cancel').parse():
1775
            return redirect('..')
1776
        if not form.is_submitted() or form.has_errors():
1777
            get_response().breadcrumb.append(('delete', _('Delete')))
1778
            html_top('hosts', title = _('Delete Host'))
1779
            '<h2>%s : %s</h2>' % (_('Delete Host'), self.host.label)
1780
            form.render()
1781
        else:
1782
            self.host.remove_self()
1783
            write_apache2_vhosts()
1784
            return redirect('..')
1785

  
1786

  
1787
class HostsDirectory(Directory):
1788
    _q_exports = ['', 'new']
1789

  
1790
    def _q_index [html] (self):
1791
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1792
        html_top('hosts', title = _('Hosts'))
1793
        """<ul id="nav-hosts-admin">
1794
          <li><a href="new">%s</a></li>
1795
        </ul>""" % _('New Host')
1796

  
1797
        '<ul class="biglist">'
1798

  
1799
        for host in Host.select(lambda x: x.name != 'larpe', order_by = 'label'):
1800
            if not host.name:
1801
                continue
1802
            if not hasattr(host, str('scheme')):
1803
                host.scheme = str('http')
1804
            '<li>'
1805
            '<strong class="label">%s</strong>' % host.label
1806
            if hasattr(host, str('new_url')) and host.new_url:
1807
                url = host.new_url
1808
            else:
1809
                # Compat with older Larpe versions
1810
                url = '%s://%s%s/' % (host.scheme, host.reversed_hostname, get_request().environ['SCRIPT_NAME'])
1811
                if host.reversed_directory is not None:
1812
                    url += '%s/' % host.reversed_directory
1813
            '<br /><a href="%s">%s</a>' % (url, url)
1814
            '<p class="commands">'
1815
            command_icon('%s/' % host.id, 'edit')
1816
            command_icon('%s/delete' % host.id, 'remove')
1817
            '</p></li>'
1818
        '</ul>'
1819

  
1820
    def new [html] (self):
1821
        if not os.path.isdir(os.path.join(get_publisher().app_dir, str('idp'))):
1822
            html_top('hosts', title = _('New Host'))
1823
            html = '<h2>%s</h2>' % _('New Host')
1824
            html += 'You must <a href="%s/admin/settings/liberty_idp/">' % misc.get_root_url()
1825
            html += 'configure an Identity Provider</a> first<br /><br />'
1826
            html += '<a href="."><input type="button" value="%s" /></a>' % _('Back')
1827
            return html
1828

  
1829
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1830
        get_response().breadcrumb.append(('new', _('New')) )
1831
        host = Host()
1832
        host.store()
1833
#        configuration_assistant = ConfigurationAssistant(host)
1834
        return redirect('%s/configuration_assistant/start' % host.id)
1835

  
1836
    def _q_lookup(self, component):
1837
        get_response().breadcrumb.append(('hosts/', _('Hosts')))
1838
        return HostPage(component)
1839

  
larpe/branches/idwsf/larpe/admin/liberty_utils.py
1
import os
2

  
3
def set_provider_keys(private_key_path, public_key_path):
4
    # use system calls for openssl since PyOpenSSL doesn't expose the
5
    # necessary functions.
6
    if os.system('openssl version > /dev/null 2>&1') == 0:
7
        os.system('openssl genrsa -out %s 2048' % private_key_path)
8
        os.system('openssl rsa -in %s -pubout -out %s' % (private_key_path, public_key_path))
9

  
10

  
11
def get_metadata(cfg):
12
    prologue = """\
13
<?xml version="1.0"?>
14
<EntityDescriptor
15
    providerID="%(provider_id)s"
16
    xmlns="urn:liberty:metadata:2003-08">""" % cfg
17

  
18
    sp_head = """
19
  <SPDescriptor protocolSupportEnumeration="urn:liberty:iff:2003-08">"""
20

  
21
    signing_public_key = ''
22
    if cfg.has_key('signing_public_key') and cfg['signing_public_key']:
23
        if 'CERTIF' in cfg['signing_public_key']:
24
            signing_public_key = """
25
        <KeyDescriptor use="signing">
26
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
27
            <ds:X509Data><ds:X509Certificate>%s</ds:X509Certificate></ds:X509Data>
28
          </ds:KeyInfo>
29
        </KeyDescriptor>""" % cfg['signing_public_key']
30
        elif 'KEY' in cfg['signing_public_key']:
31
            signing_public_key = """
32
        <KeyDescriptor use="signing">
33
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
34
            <ds:KeyValue>%s</ds:KeyValue>
35
          </ds:KeyInfo>
36
        </KeyDescriptor>""" % cfg['signing_public_key']
37

  
38
    sp_body = """
39
    <AssertionConsumerServiceURL id="AssertionConsumerServiceURL1" isDefault="true">%(base_url)s/assertionConsumer</AssertionConsumerServiceURL>
40

  
41
    <SoapEndpoint>%(base_url)s/soapEndpoint</SoapEndpoint>
42

  
43
    <SingleLogoutServiceURL>%(base_url)s/singleLogout</SingleLogoutServiceURL>
44
    <SingleLogoutServiceReturnURL>%(base_url)s/singleLogoutReturn</SingleLogoutServiceReturnURL>
45
    <SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-idp-http</SingleLogoutProtocolProfile>
46
    <SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-sp-soap</SingleLogoutProtocolProfile>
47
    <SingleLogoutProtocolProfile>http://projectliberty.org/profiles/slo-sp-http</SingleLogoutProtocolProfile>
48

  
49
    <FederationTerminationServiceURL>%(base_url)s/federationTermination</FederationTerminationServiceURL>
50
    <FederationTerminationServiceReturnURL>%(base_url)s/federationTerminationReturn</FederationTerminationServiceReturnURL>
51
    <FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-idp-soap</FederationTerminationNotificationProtocolProfile>
52
    <FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-idp-http</FederationTerminationNotificationProtocolProfile>
53
    <FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-sp-soap</FederationTerminationNotificationProtocolProfile>
54
    <FederationTerminationNotificationProtocolProfile>http://projectliberty.org/profiles/fedterm-sp-http</FederationTerminationNotificationProtocolProfile>
55

  
56
    <AuthnRequestsSigned>true</AuthnRequestsSigned>
57

  
58
  </SPDescriptor>""" % cfg
59

  
60
    orga = ''
61
    if cfg.get('organization_name'):
62
        orga = """
63
  <Organization>
64
    <OrganizationName>%s</OrganizationName>
65
  </Organization>""" % unicode(cfg['organization_name'], 'iso-8859-1').encode('utf-8')
66

  
67
    epilogue = """
68
</EntityDescriptor>"""
69

  
70
    return '\n'.join([prologue, sp_head, signing_public_key, sp_body, orga, epilogue])
71

  
72

  
73

  
74
def get_saml2_metadata(cfg):
75
    prologue = """\
76
<?xml version="1.0"?>
77
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
78
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
79
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
80
    entityID="%(saml2_provider_id)s">""" % cfg
81

  
82
    sp_head = """
83
  <SPSSODescriptor
84
      AuthnRequestsSigned="true"
85
      protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">"""
86

  
87
    signing_public_key = ''
88
    if cfg.has_key('signing_public_key') and cfg['signing_public_key']:
89
        if 'CERTIF' in cfg['signing_public_key']:
90
            signing_public_key = """
91
        <KeyDescriptor use="signing">
92
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
93
            <ds:X509Data><ds:X509Certificate>%s</ds:X509Certificate></ds:X509Data>
94
          </ds:KeyInfo>
95
        </KeyDescriptor>""" % cfg['signing_public_key']
96
        elif 'KEY' in cfg['signing_public_key']:
97
            signing_public_key = """
98
        <KeyDescriptor use="signing">
99
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
100
            <ds:KeyValue>%s</ds:KeyValue>
101
          </ds:KeyInfo>
102
        </KeyDescriptor>""" % cfg['signing_public_key']
103

  
104
    sp_body = """
105
    <AssertionConsumerService isDefault="true" index="0"
106
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
107
      Location="%(saml2_base_url)s/singleSignOnArtifact" />
108
    <SingleLogoutService
109
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
110
      Location="%(saml2_base_url)s/singleLogoutSOAP" />
111
    <SingleLogoutService
112
      Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
113
      Location="%(saml2_base_url)s/singleLogout"
114
      ResponseLocation="%(saml2_base_url)s/singleLogoutReturn" />
115

  
116
  </SPSSODescriptor>""" % cfg
117
 
118
    orga = ''
119
    if cfg.get('organization_name'):
120
        orga = """
121
  <Organization>
122
    <OrganizationName>%s</OrganizationName>
123
  </Organization>""" % unicode(cfg['organization_name'], 'iso-8859-1').encode('utf-8')
124

  
125
    epilogue = """
126
</EntityDescriptor>"""
127

  
128
    return '\n'.join([prologue, sp_head, signing_public_key, sp_body, orga, epilogue])
129

  
larpe/branches/idwsf/larpe/admin/logger.ptl
1
import random
2

  
3
from quixote import get_publisher, get_request, get_response, redirect
4
from quixote.directory import Directory
5

  
6
from larpe.qommon.form import *
7
from larpe.qommon import template
8

  
9
from qommon import logger
10
from larpe import misc
11
from larpe.users import User
12

  
13
from menu import *
14

  
15
class ByUserDirectory(Directory):
16
    def _q_lookup(self, component):
17
        return ByUserPages(component)
18

  
19

  
20
class LoggerDirectory(Directory):
21
    _q_exports = ['', 'download', 'by_user']
22

  
23
    by_user = ByUserDirectory()
24

  
25
    def _q_index [html] (self):
26
        get_response().breadcrumb.append( ('logger/', _('Logs')) )
27
        html_top('logger', title = _('Logs'))
28
        request = get_request()
29
        logfile = request.get_field('logfile', 'larpe.log')
30
        if not logfile.startswith(str('larpe.log')) or str('/') in str(logfile):
31
            return template.error_page(_('Bad log file: %s') % logfile)
32
        logfilename = str(os.path.join(get_publisher().app_dir, logfile))
33

  
34
        if not os.path.exists(logfilename):
35
            _('Nothing to show')
36
        else:
37
            if logfile:
38
                '<a href="download?logfile=%s">%s</a>' % (logfile, _('Download Raw Log File'))
39
            else:
40
                '<a href="download">%s</a>' % _('Download Raw Log File')
41

  
42
            user_color_keys = {}
43
            last_date = None
44
            '<table id="logs">\n'
45
            '<thead> <tr>'
46
            ' <th>%s</th>' % _('Time')
47
            ' <th>%s</th>' % _('User')
48
            ' <th>%s</th>' % _('Message')
49
            '<tr></thead>\n'
50
            '<tbody>\n'
51
            for line in open(logfilename):
52
                d = logger.readline(line)
53
                user_color_key = d['user_id']
54
                if user_color_key == 'anonymous':
55
                    user_color_key += d['ip']
56
                if not user_color_keys.has_key(user_color_key):
57
                    user_color_keys[user_color_key] = ''.join(
58
                        ['%x' % random.randint(0xc, 0xf) for x in range(3)])
59
                '<tr class="level-%s" style="background: #%s;">' % (
60
                        d['level'].lower(), user_color_keys[user_color_key])
61
                if (last_date != d['date']):
62
                    ' <td class="time">%s&nbsp;%s</td>' % (d['date'], d['hour'][:-4])
63
                    last_date = d['date']
64
                else:
65
                    ' <td class="time">%s</td>' % (d['hour'][:-4])
66
                if d['user_id'] ==  'anonymous':
67
                    userlabel = _('Anonymous')
68
                    ip = d['ip']
69
                    ' <td class="userlabel"><span title="%s">%s</span></td>' % (ip, userlabel)
70
                else:
71
                    try:
72
                        user = User.get(d['user_id'])
73
                    except KeyError:
74
                        userlabel = _('Unknown')
75
                    else:
76
                        if user.name is not None:
77
                            userlabel = htmltext(user.name.replace(str(' '), str('&nbsp;')))
78
                        else:
79
                            userlabel = _('Unknown')
80
                    ' <td class="userlabel">%s</td>' % userlabel
81
                ' <td class="message">%s</td>' % d['message']
82
                '</tr>\n'
83
            '</tbody>\n'
84
            '</table>\n'
85

  
86
        logfiles = [x for x in os.listdir(get_publisher().app_dir) if x.startswith(str('larpe.log'))]
87
        if len(logfiles) > 1:
88
            options = []
89
            for lfile in logfiles:
90
                firstline = open(os.path.join(get_publisher().app_dir, lfile)).readline()
91
                d = logger.readline(firstline)
92
                if not d:
93
                    continue
94
                if logfile == lfile:
95
                    selected = 'selected="selected" '
96
                else:
97
                    selected = ''
98
                options.append({'selected': selected, 'lfile': lfile,
99
                        'date': '%s %s' % (d['date'], d['hour'])})
100

  
101
            '<form id="other-log-select">'
102
            _('Select another logfile:')
103
            '<select name="logfile">'
104
            options.sort(lambda x,y: cmp(x['date'], y['date']))
105
            options.reverse()
106
            for option in options:
107
                option['since'] = str(_('Since: %s') % option['date'])[:-4]
108
                '<option value="%(lfile)s"%(selected)s>%(since)s</option>' % option
109
            '</select>'
110
            '<input type="submit" value="%s" />' % _('Submit')
111

  
112

  
113
    def download(self):
114
        request = get_request()
115
        logfile = request.get_field('logfile', 'larpe.log')
116
        if not logfile.startswith(str('larpe.log')) or str('/') in logfile:
117
            return template.error_page(_('Bad log file: %s') % logfile)
118
        logfilename = os.path.join(get_publisher().app_dir, logfile)
119
        response = get_response()
120
        response.set_content_type('text/x-log', 'iso-8859-1')
121
        response.set_header('content-disposition', 'attachment; filename=%s' % logfile)
122
        return open(logfilename).read()
123

  
124

  
125
class ByUserPages(Directory):
126
    _q_exports = ['']
127

  
128
    def __init__(self, component):
129
        try:
130
            self.user = User.get(component)
131
        except KeyError:
132
            raise TraversalError()
133

  
134
    def _q_index [html] (self):
135
        html_top('logger', title = _('Logs'))
136
        '<h2>%s - %s</h2>' % (_('User'), self.user.name)
137

  
138
        last_date = None
139
        '<table id="logs">'
140
        '<thead> <tr>'
141
        ' <th>%s</th>' % _('Time')
142
        ' <th>%s</th>' % _('Message')
143
        '<tr></thead>'
144
        '<tbody>'
145
        logfile = get_publisher().get_app_logger_filename()
146
        if os.path.exists(logfile):
147
            for line in open(logfile):
148
                d = logger.readline(line)
149
                if d['user_id'] != str(self.user.id):
150
                    continue
151
                '<tr>'
152
                if (last_date != d['date']):
153
                    ' <td class="time">%s&nbsp;%s</td>' % (d['date'], d['hour'][:-4])
154
                    last_date = d['date']
155
                else:
156
                    ' <td class="time">%s</td>' % (d['hour'][:-4])
157
                ' <td><a href="%s">%s</a></td>' % (d['url'], d['message'])
158
                '</tr>'
159
        '</tbody>'
160
        '</table>'
161

  
larpe/branches/idwsf/larpe/admin/menu.ptl
1
from quixote import get_request, get_response, get_session, get_publisher
2

  
3
from larpe import storage
4
from larpe import misc
5
from larpe.users import User
6
from larpe.hosts import Host
7

  
8
items = [
9
    ('hosts', N_('Hosts')),
10
    ('users', N_('Users')),
11
    ('settings', N_('Settings')),
12
    ('logger', N_('Logs')),
13
    # FIXME : use get_request().environ['SCRIPT_NAME']) ?
14
    ('/', N_('Liberty Alliance Reverse Proxy'))]
15

  
16
def generate_header_menu [html] (selected = None):
17
    s = ["""<ul id="menu">\n"""]
18
    base_url = get_request().environ['SCRIPT_NAME'] + '/admin'
19
    features = get_publisher().cfg.get('misc', {}).get('features', 'both')
20
    show_logger = get_publisher().cfg.get('debug', {}).get('logger', False)
21
    for k, v in items:
22
        if k == '/':
23
            continue # skip root
24
        if k == 'logger' and not show_logger:
25
            continue
26
        if k == selected:
27
            s.append('<li class="active">')
28
        else:
29
            s.append('<li>')
30
        s.append('<a href="%s/%s/">%s</a></li>\n' % (base_url, k, _(v)))
31
    s.append('</ul>\n')
32
    return ''.join(s)
33

  
34
def generate_user_info [html] ():
35
    hosts = Host.select(lambda x: x.name == 'larpe')
36
    if not hosts:
37
        return
38
    host = hosts[0]
39
    user = get_session().get_user(host.provider_id)
40
    if user and user.name:
41
        logout_url = '%s/liberty/%s/logout' % (get_request().environ['SCRIPT_NAME'], host.name)
42
        """<ul class="user-info">
43
  <li class="ui-name">%s</li>
44
  <li class="ui-logout"><a href="%s">%s</a></li>
45
</ul>""" % (user.name, logout_url, _('logout'))
46

  
47

  
48
def html_top [html] (section, title = None, scripts = None):
49
    header_menu = generate_header_menu(section)
50
    user_info = generate_user_info()
51
    subtitle = ''
52
    for s in items:
53
        if s[0] == section:
54
            subtitle = _(s[1])
55
    if not title:
56
        title = ''
57
    else:
58
        title = ' - ' + title
59
    if not scripts:
60
        scripts = ''
61
    else:
62
        scripts = '\n'.join(['<script src="%s" type="text/javascript"></script>' % x for x in scripts])
63

  
64
    sitetitle = _('Larpe Administration')
65
#    if title:
66
#        sitetitle += ' - '
67

  
68
    admin_ezt = True
69
    get_response().filter.update(locals())
70

  
71
def error_page [html] (section, error):
72
    html_top(section, title = _('Error'))
73
    '<div id="error-page">'
74
    '<h2>%s</h2>' % _('Error')
75
    '<p>%s</p>' % error
76
    '</div>'
77

  
78
def command_icon [html] (url, type, label = None, icon = None):
79
    script_name = get_request().environ['SCRIPT_NAME']
80
    icons = {
81
        'edit': 'stock_edit_16.png',
82
        'add': 'stock_add_16.png',
83
        'remove': 'stock_remove_16.png',
84
        'duplicate': 'stock_copy_16.png',
85
        'view': 'view_16.png',
86
    }
87
    labels = {
88
        'add': N_('Add'),
89
        'edit': N_('Edit'),
90
        'remove': N_('Remove'),
91
        'duplicate': N_('Duplicate'),
92
        'view': N_('View'),
93
        }
94
    if not label:
95
        label = _(labels[str(type)])
96
    if not icon:
97
        icon = icons[str(type)]
98
    if url:
99
        '''<span class="%(type)s">
100
  <a href="%(url)s"><img src="%(script_name)s/larpe/images/%(icon)s" alt="%(label)s" title="%(label)s" /></a>
101
</span>''' % locals()
102
    else:
103
        # no url -> image button
104
        '''<span class="%(type)s">
105
  <input type="image" src="%(script_name)s/larpe/images/%(icon)s" alt="%(label)s" title="%(label)s" />
106
</span>''' % locals()
107

  
larpe/branches/idwsf/larpe/admin/root.ptl
1
import os
2

  
3
from quixote import get_session, get_session_manager, get_publisher, get_request, get_response
4
from quixote.directory import Directory, AccessControlled
5

  
6
import hosts
7
import users
8
import settings
9
import logger
10
from larpe.admin.menu import html_top
11

  
12
from larpe import errors
13
from larpe import misc
14

  
15
def gpl [html] ():
16
    """<p>This program is free software; you can redistribute it and/or modify it
17
    under the terms of the GNU General Public License as published by the Free
18
    Software Foundation; either version 2 of the License, or (at your option)
19
    any later version.</p>
20

  
21
    <p>This program is distributed in the hope that it will be useful, but
22
    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
23
    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
24
    for more details.</p>
25

  
26
    <p>You should have received a copy of the GNU General Public License along with
27
    this program; if not, write to the Free Software Foundation, Inc., 59 Temple
28
    Place - Suite 330, Boston, MA  02111-1307, USA.</p>
29
    """
30

  
31

  
32
class RootDirectory(AccessControlled, Directory):
33
    _q_exports = ['', 'hosts', 'users', 'settings', 'logger']
34

  
35
    hosts = hosts.HostsDirectory()
36
    users = users.UsersDirectory()
37
    settings = settings.SettingsDirectory()
38
    logger = logger.LoggerDirectory()
39

  
40
    def _q_access(self):
41
        # FIXME : this block should be moved somewhere else
42
        get_publisher().reload_cfg()
43
        if not get_publisher().cfg.has_key('proxy_hostname'):
44
            get_publisher().cfg['proxy_hostname'] = get_request().get_server().split(':')[0]
45
        get_publisher().write_cfg()
46

  
47
        response = get_response()
48
        if not hasattr(response, 'breadcrumb'):
49
            response.breadcrumb = [ ('../admin/', _('Administration')) ]
50

  
51
        # Cheater
52
        if os.path.exists(os.path.join(get_publisher().app_dir, 'ADMIN_FOR_ALL')):
53
            return
54

  
55
        # No admin user created yet, free access
56
        user_list = users.User.select(lambda x: x.is_admin)
57
        if not user_list:
58
            return
59

  
60
        host_list = hosts.Host.select(lambda x: x.name == 'larpe')
61
        if host_list:
62
            host = host_list[0]
63
        else:
64
            raise errors.AccessForbiddenError()
65
        user = get_session().get_user(host.provider_id)
66
        if user:
67
            if not user.name or not user.is_admin:
68
                raise errors.AccessForbiddenError()
69
        else:
70
            raise errors.AccessUnauthorizedError()
71

  
72

  
73
    def _q_index [html] (self):
74
        html_top('/')
75
        gpl()
76

  
larpe/branches/idwsf/larpe/admin/settings.ptl
1
import cStringIO
2
import cPickle
3
import re
4
import os
5
import lasso
6
import glob
7
import zipfile
8

  
9
from quixote import get_publisher, get_request, get_response, redirect
10
from quixote.directory import Directory, AccessControlled
11

  
12
from menu import *
13

  
14
from larpe.qommon.form import *
15
from larpe.qommon.misc import get_abs_path
16
from larpe.qommon.admin.emails import EmailsDirectory
17

  
18
from larpe import misc
19
from larpe.hosts import Host
20
from larpe.admin.liberty_utils import *
21

  
22
class LibertyIDPDir(Directory):
23
    _q_exports = ['', ('metadata.xml', 'metadata')]
24

  
25
    def _q_index [html] (self):
26
        form = Form(enctype="multipart/form-data")
27
        form.add(FileWidget, "metadata", title = _("Metadata"), required=True)
28
        form.add(FileWidget, "publickey", title = _("Public Key"), required=False)
29
        form.add(FileWidget, "cacertchain", title = _("CA Certificate Chain"), required=False)
30
        form.add_submit("submit", _("Submit"))
31

  
32
        if not form.is_submitted() or form.has_errors():
33
            html_top('settings', title = _('New Identity Provider'))
34
            "<h2>%s</h2>" % _('New Identity Provider')
35
            form.render()
36
        else:
37
            self.submit_new(form)
38

  
39
    def submit_new(self, form, key_provider_id = None):
40
        metadata, publickey, cacertchain = None, None, None
41
        if form.get_widget('metadata').parse():
42
            metadata = form.get_widget('metadata').parse().fp.read()
43
        if form.get_widget('publickey').parse():
44
            publickey = form.get_widget('publickey').parse().fp.read()
45
        if form.get_widget('cacertchain').parse():
46
            cacertchain = form.get_widget('cacertchain').parse().fp.read()
47

  
48
        if not key_provider_id:
49
            try:
50
                provider_id = re.findall(r'(provider|entity)ID="(.*?)"', metadata)[0][1]
51
            except IndexError:
52
                return error_page(_('Bad metadata'))
53
            key_provider_id = provider_id.replace(str('://'), str('-')).replace(str('/'), str('-'))
54

  
55
        dir = get_abs_path(os.path.join('idp', key_provider_id))
56
        if not os.path.isdir(dir):
57
            os.makedirs(dir)
58

  
59
        if metadata:
60
            metadata_fn = os.path.join(dir, 'metadata.xml')
61
            open(metadata_fn, 'w').write(metadata)
62
        if publickey:
63
            publickey_fn = os.path.join(dir, 'public_key')
64
            open(publickey_fn, 'w').write(publickey)
65
        else:
66
            publickey_fn = None
67
        if cacertchain:
68
            cacertchain_fn = os.path.join(dir, 'ca_cert_chain.pem')
69
            open(cacertchain_fn, 'w').write(cacertchain)
70
        else:
71
            cacertchain_fn = None
72

  
73
        p = lasso.Provider(lasso.PROVIDER_ROLE_IDP, metadata_fn, publickey_fn, None)
74

  
75
        try:
76
            misc.get_provider_label(p)
77
            get_publisher().cfg['idp'] = key_provider_id
78
            get_publisher().write_cfg()
79
        except TypeError:
80
            if metadata:
81
                os.unlink(metadata_fn)
82
            if publickey:
83
                os.unlink(publickey_fn)
84
            if cacertchain:
85
                os.unlink(cacertchain_fn)
86
            return error_page(_('Bad metadata'))
87

  
88
        redirect('..')
89

  
90
    def metadata(self):
91
        response = get_response()
92
        response.set_content_type('text/xml', 'utf-8')
93
        get_publisher().reload_cfg()
94
        if get_publisher().cfg['idp']:
95
            idp_metadata = os.path.join(get_abs_path('idp'), get_publisher().cfg['idp'], 'metadata.xml')
96
            return unicode(open(idp_metadata).read(), 'utf-8')
97
        return 'No IDP is configured'
98

  
99

  
100
class EmailsDirectory(Directory):
101
    emails = []
102

  
103
    def __init__(self):
104
        self._q_exports = ['', 'options'] + [x[0] for x in self.emails]
105

  
106
    def options [html] (self):
107
        form = Form(enctype="multipart/form-data")
108
        emails = get_publisher().cfg.get('emails', {})
109
        form.add(StringWidget, 'smtp_server', title = _('SMTP Server'),
110
                required = False, value = emails.get('smtp_server', ''))
111
        form.add(StringWidget, 'from', title = _('Email Sender'),
112
                required = True, value = emails.get('from', 'larpe@localhost'))
113
        form.add(StringWidget, 'reply_to', title = _('Reply-To Address'),
114
                required = False, value = emails.get('reply_to'))
115

  
116
        form.add_submit("submit", _("Submit"))
117
        form.add_submit("cancel", _("Cancel"))
118
        if form.get_widget('cancel').parse():
119
            return redirect('.')
120

  
121
        if not form.is_submitted() or form.has_errors():
122
            html_top('settings', title = _('Emails'))
123
            "<h2>%s</h2>" % _('General Options')
124
            form.render()
125
        else:
126
            self.options_submit(form)
127
            redirect('.')
128

  
129
    def options_submit(self, form):
130
        get_publisher().reload_cfg()
131
        if not get_publisher().cfg.has_key('emails'):
132
            get_publisher().cfg['emails'] = {}
133
        for k in ('smtp_server', 'from', 'reply_to'):
134
            get_publisher().cfg['emails'][k] = form.get_widget(k).parse()
135
        get_publisher().write_cfg()
136

  
137
    def _q_index [html] (self):
138
        html_top('settings', title = _('Emails'))
139
        '<h2>%s</h2>' % _('Emails')
140

  
141
        '<ul>'
142
        '<li><a href="options">%s</a></li>' % _('General Options')
143
        for email_key, email_label in self.emails:
144
            '<li><a href="%s">%s %s</a></li>' % (email_key,
145
                    _('Custom Email:'), _(email_label))
146
        '</ul>'
147

  
148
        '<p>'
149
        '<a href="..">%s</a>' % _('Back')
150
        '</p>'
151

  
152

  
153
    def email [html] (self, email_key, email_label, hint = None, check_template = None,
154
                enabled = True):
155
        emails_cfg = get_publisher().cfg.get('emails', {})
156
        cfg_key = 'email-%s' % email_key
157

  
158
        form = Form(enctype='multipart/form-data')
159
        form.add(CheckboxWidget, cfg_key + '_enabled', title = _('Enabled Email'),
160
                value = emails_cfg.get(cfg_key + '_enabled', True), default = enabled)
161
        form.add(StringWidget, cfg_key + '_subject', title = _('Subject'),
162
                value = emails_cfg.get(cfg_key + '_subject', ''))
163
        form.add(TextWidget, cfg_key, title = email_label, value = emails_cfg.get(cfg_key),
164
                cols = 80, rows = 10, hint = hint)
165
        form.add_submit('submit', _('Submit'))
166
        form.add_submit('restore-default', _('Restore default email'))
167
        form.add_submit('cancel', _('Cancel'))
168

  
169
        if form.get_submit() == 'cancel':
170
            return redirect('.')
171

  
172
        if form.get_submit() == 'restore-default':
173
            self.email_submit(None, cfg_key)
174
            return redirect('.')
175

  
176
        if form.is_submitted() and not form.has_errors():
177
            if self.email_submit(form, cfg_key, check_template):
178
                return redirect('.')
179
            form.set_error(cfg_key, _('Invalid template'))
180

  
181
        html_top('settings', title = _('Emails'))
182
        '<h2>%s - %s</h2>' % (_('Email'), email_label)
183
        form.render()
184

  
185
    def email_submit(self, form, cfg_key, check_template = None):
186
        get_publisher().reload_cfg()
187
        if not get_publisher().cfg.has_key('emails'):
188
            get_publisher().cfg['emails'] = {}
189
        if form:
190
            template = form.get_widget(cfg_key).parse()
191
            if check_template and not check_template(template):
192
                return False
193
            get_publisher().cfg['emails'][str(cfg_key)] = template
194
            get_publisher().cfg['emails'][str(cfg_key + '_enabled')] = form.get_widget(
195
                    cfg_key + '_enabled').parse()
196
            get_publisher().cfg['emails'][str(cfg_key + '_subject')] = form.get_widget(
197
                    cfg_key + '_subject').parse()
198
        else:
199
            get_publisher().cfg['emails'][str(cfg_key)] = None
200
        get_publisher().write_cfg()
201
        return True
202

  
203

  
204
class SettingsDirectory(AccessControlled, Directory):
205
    _q_exports = ['', 'liberty_sp', 'liberty_idp', 'domain_names', 'apache2_configuration_generation',
206
                  'language', 'emails', 'proxy' ]
207

  
208
    liberty_idp = LibertyIDPDir()
209
    emails = EmailsDirectory()
210

  
211
    def _q_access(self):
212
        get_response().breadcrumb.append( ('settings/', _('Settings')) )
213

  
214
    def _q_index [html] (self):
215
        get_publisher().reload_cfg()
216
        html_top('settings', title = _('Settings'))
217

  
218
        if lasso.SAML2_SUPPORT:
219
            '<h2>%s</h2>' % _('Liberty Alliance & SAML 2.0 Service Provider')
220
        else:
221
            '<h2>%s</h2>' % _('Liberty Alliance Service Provider')
222
        '<dl> <dt><a href="liberty_sp">%s</a></dt> <dd>%s</dd>' % (
223
                _('Service Provider'), _('Configure Larpe as a Service Provider'))
224

  
225
        hosts = Host.select(lambda x: x.name == 'larpe')
226
        if hosts:
227
            self.host = hosts[0]
228

  
229
            if lasso.SAML2_SUPPORT and self.host.saml2_metadata is not None:
230
                metadata_url = '%s/metadata.xml' % self.host.saml2_base_url
231
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
232
                        metadata_url,
233
                        _('Service Provider Metadata'),
234
                        _('Download Service Provider SAML 2.0 Metadata file'))
235

  
236
            if self.host.metadata is not None:
237
                metadata_url = '%s/metadata.xml' % self.host.base_url
238
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
239
                        metadata_url,
240
                        _('Service Provider Metadata'),
241
                        _('Download Service Provider ID-FF 1.2 Metadata file'))
242

  
243
            if self.host.public_key is not None:
244
                public_key_url = '%s/public_key' % self.host.base_url
245
                '<dt><a href="%s">%s</a></dt> <dd>%s</dd>' % (
246
                        public_key_url,
247
                        _('Public key'),
248
                        _('Download Service Provider SSL Public Key file'))
249

  
250
        if lasso.SAML2_SUPPORT:
251
            '<h2>%s</h2>' % _('Liberty Alliance & SAML 2.0 Identity Provider')
252
        else:
253
            '<h2>%s</h2>' % _('Liberty Alliance Identity Provider')
254

  
255
        '<dl>'
256

  
257
        '<dt><a href="liberty_idp/">%s</a></dt> <dd>%s</dd>' % (
258
               _('Identity Provider'), _('Configure an identity provider'))
259

  
260
        if get_publisher().cfg.has_key('idp'):
261
            '<dt><a href="liberty_idp/metadata.xml">%s</a></dt> <dd>%s</dd>' % (
262
                    _('Identity Provider metadatas'), _('See current identity provider metadatas'))
263

  
264
        '</dl>'
265

  
266
        '<h2>%s</h2>' % _('Global parameters for the sites')
267

  
268
        '<dl>'
269
        '<dt><a href="domain_names">%s</a></dt> <dd>%s</dd>' % (
270
                _('Domain name'), _('Configure the base domain name for the sites'))
271
        '<dt><a href="apache2_configuration_generation">%s</a></dt> <dd>%s</dd>' % (
272
                _('Apache 2 configuration generation'), _('Customise Apache 2 configuration generation'))
273
        '<dt><a href="proxy">%s</a></dt> <dd>%s</dd>' % (
274
                _('Proxy'), _('Connect to the sites through a web proxy'))
275
        '</dl>'
276

  
277
        '<h2>%s</h2>' % _('Customisation')
278

  
279
        '<dl>'
280
        '<dt><a href="language">%s</a></dt> <dd>%s</dd>' % (
281
                _('Language'), _('Configure site language'))
282
        '<dt><a href="emails/">%s</a></dt> <dd>%s</dd>' % (
283
                _('Emails'), _('Configure email settings'))
284
        '</dl>'
285
#        '<h2>%s</h2>' % _('Misc')
286
#        '<dl>'
287
#        '<dt><a href="misc">%s</a></dt> <dd>%s</dd>' % (
288
#                _('Misc'), _('Configure misc options'))
289
#        '<dt><a href="debug_options">%s</a></dt> <dd>%s</dd>' % (
290
#                _('Debug Options'), _('Configure options useful for debugging'))
291
#        '</dl>'
292

  
293

  
294
    def liberty_sp [html] (self):
295
        get_publisher().reload_cfg()
296

  
297
        # Get the host object for the reverse proxy
298
        hosts = Host.select(lambda x: x.name == 'larpe')
299
        if hosts:
300
            self.host = hosts[0]
301
        else:
302
            self.host = Host()
303
            self.host.reversed_hostname = get_publisher().cfg[str('proxy_hostname')]
304

  
305
        form = Form(enctype='multipart/form-data')
306
        form.add(StringWidget, 'organization_name', title=_('Organisation Name'), size=50,
307
                required = True, value = self.host.organization_name)
308
        form.add_submit('submit', _('Submit'))
309
        form.add_submit('cancel', _('Cancel'))
310
        if form.get_widget('cancel').parse():
311
            return redirect('.')
312
        if not form.is_submitted() or form.has_errors():
313
            html_top('settings', title = _('Service Provider Configuration'))
314
            '<h2>%s</h2>' % _('Service Provider Configuration')
315
            form.render()
316
        else:
317
            self.liberty_sp_submit(form)
318
            redirect('.')
319

  
320
    def liberty_sp_submit(self, form):
321
        get_publisher().reload_cfg()
322
        metadata_cfg = {}
323

  
324
        f = 'organization_name'
325
        if form.get_widget(f):
326
            setattr(self.host, f, form.get_widget(f).parse())
327

  
328
        metadata_cfg['organization_name'] = self.host.organization_name
329

  
330
        self.host.name = 'larpe'
331
        
332
        # Liberty Alliance / SAML parameters
333
        base_url = '%s/liberty/%s/liberty' % (misc.get_root_url(), self.host.name)
334
        metadata_cfg['base_url'] = base_url
335
        self.host.base_url = base_url
336

  
337
        if lasso.SAML2_SUPPORT:
338
            saml2_base_url = '%s/liberty/%s/saml' % (misc.get_root_url(), self.host.name)
339
            metadata_cfg['saml2_base_url'] = saml2_base_url
340
            self.host.saml2_base_url = saml2_base_url
341

  
342
        provider_id = '%s/metadata' % base_url
343
        metadata_cfg['provider_id'] = provider_id
344
        self.host.provider_id = provider_id
345

  
346
        if lasso.SAML2_SUPPORT:
347
            saml2_provider_id = '%s/metadata' % saml2_base_url
348
            metadata_cfg['saml2_provider_id'] = saml2_provider_id
349
            self.host.saml2_provider_id = saml2_provider_id
350

  
351
        # Storage directories
352
        site_dir = os.path.join(get_publisher().app_dir, 'sp',
353
                    self.host.reversed_hostname, self.host.name)
354
        user_dir = os.path.join(site_dir, 'users')
355
        token_dir = os.path.join(site_dir, 'tokens')
356
        for dir in (site_dir, user_dir, token_dir):
357
            if not os.path.isdir(dir):
358
                os.makedirs(dir)
359
        metadata_cfg['site_dir'] = site_dir
360
        self.host.site_dir = site_dir
361

  
362
        # Generate SSL keys
363
        private_key_path = os.path.join(site_dir, 'private_key.pem')
364
        public_key_path = os.path.join(site_dir, 'public_key')
365
        if not os.path.isfile(private_key_path) or not os.path.isfile(public_key_path):
366
            set_provider_keys(private_key_path, public_key_path)
367
        self.host.private_key = private_key_path
368
        metadata_cfg['signing_public_key'] = open(public_key_path).read()
369
        self.host.public_key = public_key_path
370
        
371
        # Write metadatas
372
        metadata_path = os.path.join(site_dir, 'metadata.xml')
373
        open(metadata_path, 'w').write(get_metadata(metadata_cfg))
374
        self.host.metadata = metadata_path
375

  
376
        if hasattr(self.host, 'saml2_provider_id'):
377
            saml2_metadata_path = os.path.join(site_dir, 'saml2_metadata.xml')
378
            open(saml2_metadata_path, 'w').write(get_saml2_metadata(metadata_cfg))
379
            self.host.saml2_metadata = saml2_metadata_path
380

  
381
        self.host.root_url = '%s/' % misc.get_root_url()
382
        self.host.return_url = '%s/admin/' % misc.get_root_url()
383

  
384
        self.host.store()
385

  
386
    def domain_names [html] (self):
387
        form = self.form_domain_name()
388

  
389
        if form.get_widget('cancel').parse():
390
            return redirect('.')
391

  
392
        if not form.is_submitted() or form.has_errors():
393
            html_top('settings', title = _('Domain name'))
394
            '<h2>%s</h2>' % _('Domain name')
395
            form.render()
396
        else:
397
            self.submit_domain_name(form)
398
            redirect('.')
399

  
400
    def form_domain_name(self):
401
        get_publisher().reload_cfg()
402
        if get_cfg('domain_names'):
403
            domain_name = get_cfg('domain_names')[0]
404
        else:
405
            domain_name = None
406

  
407
        form = Form(enctype='multipart/form-data')
408
        form.add(StringWidget, 'domain_name',
409
            title=_('Domain name for the sites'),
410
            value = domain_name)
411
        # TODO: Add the option "Both" and handle it in hosts configuration
412
        form.add(SingleSelectWidget, 'sites_url_scheme', title = _('Use HTTP or HTTPS'),
413
                value = get_cfg('sites_url_scheme'),
414
                options = [ (None, _('Same as the site')),
415
                            ('http', 'HTTP'),
416
                            ('https', 'HTTPS') ] )
417
        form.add_submit('submit', _('Submit'))
418
        form.add_submit('cancel', _('Cancel'))
419
        return form
420

  
421
    def submit_domain_name(self, form):
422
        get_publisher().reload_cfg()
423
        get_publisher().cfg['domain_names'] = [ form.get_widget('domain_name').parse() ]
424
        get_publisher().cfg['sites_url_scheme'] = form.get_widget('sites_url_scheme').parse()
425
        get_publisher().write_cfg()
426

  
427
    def apache2_configuration_generation [html] (self):
428
        get_publisher().reload_cfg()
429

  
430
        form = Form(enctype='multipart/form-data')
431
        form.add(CheckboxWidget, 'allow_config_generation',
432
                title=_('Automatically generate Apache 2 configuration for new hosts and reload Apache 2 after changes'),
433
                value = get_publisher().cfg.get(str('allow_config_generation'), True))
434
        form.add_submit('submit', _('Submit'))
435
        form.add_submit('cancel', _('Cancel'))
436
        if form.get_widget('cancel').parse():
437
            return redirect('.')
438
        if not form.is_submitted() or form.has_errors():
439
            html_top('settings', title = _('Apache 2 configuration generation'))
440
            '<h2>%s</h2>' % _('Apache 2 configuration generation')
441
            form.render()
442
        else:
443
            self.apache2_configuration_generation_submit(form)
444
            redirect('.')
445
            
446
    def apache2_configuration_generation_submit(self, form):
447
        get_publisher().reload_cfg()
448

  
449
        f = 'allow_config_generation'
450
        get_publisher().cfg[f] = form.get_widget(f).parse()
451

  
452
        get_publisher().write_cfg()
453

  
454
    def language [html] (self):
455
        form = Form(enctype='multipart/form-data')
456
        language_cfg = get_publisher().cfg.get('language', {})
457
        form.add(SingleSelectWidget, 'language', title = _('Language'),
458
                value = language_cfg.get('language'),
459
                options = [ (None, _('System Default')),
460
                            (str('en'), _('English')),
461
                            (str('fr'), _('French')) ] )
462
         
463
        form.add_submit('submit', _('Submit'))
464
        form.add_submit('cancel', _('Cancel'))
465
        if form.get_widget('cancel').parse():
466
            return redirect('.')
467

  
468
        if not form.is_submitted() or form.has_errors():
469
            html_top('settings', title = _('Language'))
470
            '<h2>%s</h2>' % _('Language')
471
            form.render()
472
        else:
473
            self.language_submit(form)
474
            redirect('.')
475

  
476
    def language_submit(self, form):
477
        get_publisher().reload_cfg()
478
        if not get_publisher().cfg.has_key('language'):
479
            get_publisher().cfg['language'] = {}
480
        for k in ('language', ):
481
            get_publisher().cfg['language'][k] = form.get_widget(k).parse()
482
        get_publisher().write_cfg()
483

  
484
    def proxy [html] (self):
485
        get_publisher().reload_cfg()
486
        form = Form(enctype='multipart/form-data')
487

  
488
        form.add(CheckboxWidget, 'use_proxy',
489
                title=_('Use a web proxy'),
490
                value = get_publisher().cfg.get(str('use_proxy'), False))
491
        form.add(StringWidget, 'proxy_ip',
492
                title=_('Proxy IP address or domain name'),
493
                value = get_publisher().cfg.get(str('proxy_ip')))
494
        form.add(StringWidget, 'proxy_port',
495
                title=_('Proxy port'),
496
                value = get_publisher().cfg.get(str('proxy_port')))
497
        form.add(StringWidget, 'proxy_user',
498
                title=_('User name'),
499
                value = get_publisher().cfg.get(str('proxy_user')))
500
        form.add(PasswordWidget, 'proxy_password',
501
                title=_('User password'),
502
                value = get_publisher().cfg.get(str('proxy_password')))
503

  
504
        form.add_submit('submit', _('Submit'))
505
        form.add_submit('cancel', _('Cancel'))
506
        if form.get_widget('cancel').parse():
507
            return redirect('.')
508

  
509
        if not form.is_submitted() or form.has_errors():
510
            html_top('settings', title = _('Proxy'))
511
            '<h2>%s</h2>' % _('Proxy')
512
            form.render()
513
        else:
514
            self.proxy_submit(form)
515
            redirect('.')
516

  
517
    def proxy_submit(self, form):
518
        get_publisher().reload_cfg()
519
        for f in ('use_proxy', 'proxy_ip', 'proxy_port', 'proxy_user', 'proxy_password'):
520
            get_publisher().cfg[f] = form.get_widget(f).parse()
521
        get_publisher().write_cfg()
522

  
523
#     def debug_options [html] (self):
524
#         form = Form(enctype="multipart/form-data")
525
#         debug_cfg = get_publisher().cfg.get('debug', {})
526
#         form.add(StringWidget, 'error_email', title = _('Email for Tracebacks'),
527
#                 value = debug_cfg.get('error_email', ''))
528
#         form.add(SingleSelectWidget, 'display_exceptions', title = _('Display Exceptions'),
529
#                 value = debug_cfg.get('display_exceptions', ''),
530
#                 options = [ (str(''), _('No display')),
531
#                             (str('text'), _('Display as Text')),
532
#                             (str('text-in-html'), _('Display as Text in HTML an error page')),
533
#                             (str('html'), _('Display as HTML')) ])
534
#         form.add(CheckboxWidget, 'logger', title = _('Logger'),
535
#                 value = debug_cfg.get('logger', False))
536
#         form.add_submit("submit", _("Submit"))
537
#         form.add_submit("cancel", _("Cancel"))
538

  
539
#         if form.get_widget('cancel').parse():
540
#             return redirect('.')
541

  
542
#         if not form.is_submitted() or form.has_errors():
543
#             html_top('settings', title = _('Debug Options'))
544
#             '<h2>%s</h2>' % _('Debug Options')
545
#             form.render()
546
#         else:
547
#             self.debug_options_submit(form)
548
#             redirect('.')
549

  
550
#     def debug_options_submit(self, form):
551
#         get_publisher().reload_cfg()
552
#         if not get_publisher().cfg.has_key('debug'):
553
#             get_publisher().cfg['debug'] = {}
554
#         for k in ('error_email', 'display_exceptions', 'logger'):
555
#             get_publisher().cfg['debug'][k] = form.get_widget(k).parse()
556
#         get_publisher().write_cfg()
557
#         get_publisher().set_config()
558

  
559

  
560
def error_page [html] (error_message):
561
    html_top(_('Error'))
562
    '<h1>%s</h1>' % _('Error')
563
    '<div class="error-page">'
564
    '<p>%s</p>' % error_message
565
    '</div>'
566

  
larpe/branches/idwsf/larpe/admin/users.ptl
1
import random
2
import lasso
3

  
4
from quixote import get_request, get_session, redirect, get_publisher
5
from quixote.directory import Directory
6

  
7
from larpe.qommon.form import *
8
from larpe.qommon import emails
9

  
10
from larpe import errors
11
from larpe import misc
12
from larpe import storage
13
from larpe.users import User
14
from larpe.hosts import Host
15

  
16
from menu import *
17

  
18
class UserUI:
19
    def __init__(self, user):
20
        self.user = user
21

  
22
    def form_new(self):
23
        form = Form(enctype="multipart/form-data")
24
        form.add(StringWidget, "name", title = _('User Name'), required = True, size=30)
25
        form.add(StringWidget, "email", title = _('Email'), required = False, size=30)
26
        form.add_submit("submit", _("Submit"))
27
        form.add_submit("cancel", _("Cancel"))
28
        return form
29

  
30
    def form_edit(self):
31
        form = Form(enctype="multipart/form-data")
32
        form.add(StringWidget, "id", title = _('User Id'), required = False, size=30,
33
                value = self.user.id, readonly = 'readonly')
34
        form.add(StringWidget, "name", title = _('User Name'), required = True, size=30,
35
                value = self.user.name)
36
        form.add(StringWidget, "email", title = _('Email'), required = False, size=30,
37
                value = self.user.email)
38
        form.add_submit("submit", _("Submit"))
39
        form.add_submit("cancel", _("Cancel"))
40
        return form
41

  
42
    def submit_form(self, form):
43
        if not self.user:
44
            self.user = User()
45
        for f in ('name', 'email'):
46
            widget = form.get_widget(f)
47
            if widget:
48
                setattr(self.user, f, widget.parse())
49
        self.user.is_admin = True
50
        self.user.store()
51

  
52

  
53
class UserPage(Directory):
54
    _q_exports = ['', 'edit', 'delete', 'token']
55

  
56
    def __init__(self, component):
57
        self.user = User.get(component)
58
        self.user_ui = UserUI(self.user)
59
        get_response().breadcrumb.append((component + '/', self.user.name))
60

  
61
    def _q_index [html] (self):
62
        html_top('users', '%s - %s' % (_('User'), self.user.name))
63
        '<h2>%s - %s</h2>' % (_('User'), self.user.name)
64
        '<div class="form">'
65
        '<div class="title">%s</div>' % _('Name')
66
        '<div class="StringWidget content">%s</div>' % self.user.name
67
        if self.user.email:
68
            '<div class="title">%s</div>' % _('Email')
69
            '<div class="StringWidget content">%s</div>' % self.user.email
70
#         if self.user.lasso_dump:
71
#             identity = lasso.Identity.newFromDump(self.user.lasso_dump)
72
#             server = misc.get_lasso_server()
73
#             if len(identity.providerIds) and server:
74
#                 '<h3>%s</h3>' % _('Liberty Alliance Details')
75
#                 '<div class="StringWidget content"><ul>'
76
#                 for pid in identity.providerIds:
77
#                     provider = server.getProvider(pid)
78
#                     label = misc.get_provider_label(provider)
79
#                     if label:
80
#                         label = '%s (%s)' % (label, pid)
81
#                     else:
82
#                         label = pid
83
#                     federation = identity.getFederation(pid)
84
#                     '<li>'
85
#                     _('Account federated with %s') % label
86
#                     '<br />'
87
#                     if federation.localNameIdentifier:
88
#                         _("local: ") + federation.localNameIdentifier.content
89
#                     if federation.remoteNameIdentifier:
90
#                         _("remote: ") + federation.remoteNameIdentifier.content
91
#                     '</li>'
92
#                 '</ul></div>'
93

  
94
#                 # XXX: only display this in debug mode:
95
#                 '<h4>%s</h4>' % _('Lasso Identity Dump')
96
#                 '<pre>%s</pre>' % self.user.lasso_dump
97
        '</div>'
98

  
99
    def debug [html] (self):
100
        get_response().breadcrumb.append( ('debug', _('Debug')) )
101
        html_top('users', 'Debug')
102
        "<h2>Debug - %s</h2>" % self.user.name
103
        "<pre>"
104
        self.user.lasso_dump
105
        "</pre>"
106

  
107
    def edit [html] (self):
108
        form = self.user_ui.form_edit()
109
        if form.get_widget('cancel').parse():
110
            return redirect('..')
111
        if not form.is_submitted() or form.has_errors():
112
            get_response().breadcrumb.append( ('edit', _('Edit')) )
113
            html_top('users', title = _('Edit User'))
114
            '<h2>%s</h2>' % _('Edit User')
115
            form.render()
116
        else:
117
            self.user_ui.submit_form(form)
118
            return redirect('..')
119

  
120
    def delete [html] (self):
121
        form = Form(enctype="multipart/form-data")
122
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
123
                        "You are about to irrevocably delete this user.")))
124
        form.add_submit("submit", _("Submit"))
125
        form.add_submit("cancel", _("Cancel"))
126
        if form.get_widget('cancel').parse():
127
            return redirect('..')
128
        if not form.is_submitted() or form.has_errors():
129
            get_response().breadcrumb.append(('delete', _('Delete')))
130
            html_top('users', title = _('Delete User'))
131
            '<h2>%s %s</h2>' % (_('Deleting User :'), self.user.name)
132
            form.render()
133
        else:
134
            self.user.remove_self()
135
            return redirect('..')
136

  
137
    def token [html] (self):
138
        form = Form(enctype="multipart/form-data", use_tokens = False)
139
        form.add_submit("submit", _("Generate"))
140
        form.add_submit("cancel", _("Cancel"))
141
        request = get_request()
142
        if request.form.has_key('cancel') or request.form.has_key('done'):
143
            return redirect('..')
144

  
145
        get_response().breadcrumb.append(('token', _('Identification Token')))
146

  
147
        if not form.is_submitted() or form.has_errors():
148
            html_top('users', title = _('Identification Token'))
149
            '<h2>%s</h2>' % _('Identification Token')
150
            '<p>%s</p>' % _('You are about to generate a token than can be used to federate the account.')
151
            '<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.')
152
            if self.user.identification_token:
153
                '<p>%s</p>' % _('Note that user has already been issued an identification token : %s') % self.user.identification_token
154
            form.render()
155
        else:
156
            if request.form.has_key('submit'):
157
                html_top('users', title = _('Identification Token'))
158
                token = '-'.join(['%04d' % random.randint(1, 9999) for x in range(4)])
159
                self.user.identification_token = str(token)
160
                self.user.store()
161

  
162
                '<p>'
163
                _('Identification Token for %s') % self.user.name
164
                ' : %s</p>' % self.user.identification_token
165

  
166
                form = Form(enctype="multipart/form-data", use_tokens = False)
167
                form.add_submit('done', _('Done'))
168
                if self.user.email:
169
                    form.add_submit("submit-email", _("Send by email"))
170
                form.render()
171
            else:
172
                site_url = '%s://%s%s/token?token=%s' \
173
                    % (request.get_scheme(), request.get_server(),
174
                       get_request().environ['SCRIPT_NAME'], self.user.identification_token)
175
                body = _("""You have been given an identification token.
176

  
177
Your token is %(token)s
178

  
179
Click on %(url)s to use it.
180
""") % {'token': self.user.identification_token, 'url': site_url}
181
                try:
182
                    emails.email(_('Identification Token'), body, self.user.email)
183
                except errors.EmailError, e:
184
                    html_top('users', title = _('Identification Token'))
185
                    _('Failed sending email. Check your email configuration.')
186
                    '<div class="buttons"><a href=".."><input type="button" value="%s" /></a></div><br />' % _('Back')
187
                else:
188
                    return redirect('..')
189

  
190
class UsersDirectory(Directory):
191

  
192
    _q_exports = ['', 'new']
193

  
194
    def _q_index [html] (self):
195
        get_publisher().reload_cfg()
196
        get_response().breadcrumb.append( ('users/', _('Users')) )
197
        html_top('users', title = _('Users'))
198

  
199
        
200
        if not list(Host.select(lambda x: x.name == 'larpe')):
201
            '<p>%s</p>' % _('Liberty support must be setup before creating users.')
202
        else:
203
            """<ul id="nav-users-admin">
204
              <li><a href="new">%s</a></li>
205
            </ul>""" % _('New User')
206

  
207
        debug_cfg = get_publisher().cfg.get('debug', {})
208

  
209
        users = User.select(lambda x: x.name is not None, order_by = 'name')
210

  
211
        '<ul class="biglist">'
212
        for user in users:
213
            '<li>'
214
            '<strong class="label">%s</strong>' % user.name
215
            if user.email:
216
                '<p class="details">'
217
                user.email
218
                '</p>'
219

  
220
            '<p class="commands">'
221
            command_icon('%s/' % user.id, 'view')
222
            if not user.name_identifiers:
223
                if not user.identification_token:
224
                    command_icon('%s/token' % user.id, 'token',
225
                            label = _('Identification Token'), icon = 'stock_exec_16.png')
226
                else:
227
                    command_icon('%s/token' % user.id, 'token',
228
                            label = _('Identification Token (current: %s)') % \
229
                                user.identification_token,
230
                            icon = 'stock_exec_16.png')
231
            command_icon('%s/edit' % user.id, 'edit')
232
            command_icon('%s/delete' % user.id, 'remove')
233
            if debug_cfg.get('logger', False):
234
                command_icon('../logger/by_user/%s/' % user.id, 'logs',
235
                        label = _('Logs'), icon = 'stock_harddisk_16.png')
236
            '</p></li>'
237
        '</ul>'
238

  
239
    def new [html] (self):
240
        get_response().breadcrumb.append( ('users/', _('Users')) )
241
        get_response().breadcrumb.append( ('new', _('New')) )
242
        hosts = list(Host.select(lambda x: x.name == 'larpe'))
243
        if not hosts:
244
            return error_page('users', _('Liberty support must be setup before creating users.'))
245
        host = hosts[0]
246
        # XXX: user must be logged in to get here
247
        user_ui = UserUI(None)
248
        # FIXME : should be able to use User.count(). Track fake user creations.
249
        users = User.select(lambda x: x.name is not None)
250
        first_user = (len(users) == 0)
251
        form = user_ui.form_new()
252
        if form.get_widget('cancel').parse():
253
            return redirect('.')
254

  
255
        if not form.is_submitted() or form.has_errors():
256
            html_top('users', title = _('New User'))
257
            '<h2>%s</h2>' % _('New User')
258
            form.render()
259
        else:
260
            user_ui.submit_form(form)
261
            if first_user:
262
                req = get_request()
263
                if hasattr(req, str('user')) and req.user:
264
                    user_ui.user.name_identifiers = req.user.name_identifiers
265
                    user_ui.user.lasso_dump = req.user.lasso_dump
266
                    user_ui.user.store()
267
                # TODO : SAML 2.0
268
                get_session().set_user(user_ui.user.id, host.provider_id)
269
            return redirect('.')
270
    
271
    def _q_lookup(self, component):
272
        get_response().breadcrumb.append( ('users/', _('Users')) )
273
        try:
274
            return UserPage(component)
275
        except KeyError:
276
            raise errors.TraversalError()
larpe/branches/idwsf/larpe/ctl/__init__.py
1
from start import start
2

  
larpe/branches/idwsf/larpe/ctl/start.py
1
import socket
2
import sys
3

  
4
from qommon.scgi_server import run
5

  
6
import publisher
7

  
8
def start(args):
9
    port = 3007
10
    script_name = ''
11

  
12
    i = 0
13
    while i < len(args):
14
        if args[i] == '--port':
15
            port = int(args[i+1])
16
            i += 1
17
        elif args[i] == '--silent':
18
            sys.stdout = open('/dev/null', 'w')
19
            sys.stderr = open('/dev/null', 'w')
20
        elif args[i] == '--script-name':
21
            script_name = args[i+1]
22
            i += 1
23
        i += 1
24

  
25
    try:
26
        run(publisher.LarpePublisher.create_publisher, port=port, script_name=script_name)
27
    except socket.error, e:
28
        if e[0] == 98:
29
            print >> sys.stderr, 'address already in use'
30
            sys.exit(1)
31
        raise
32
    except KeyboardInterrupt:
33
        sys.exit(1)
34

  
larpe/branches/idwsf/larpe/errors.ptl
1
from quixote import get_session, get_request, redirect
2

  
3
from qommon.errors import *
4

  
5
class AccessUnauthorizedError(AccessError):
6
    def render [html] (self):
7
        session = get_session()
8
        request = get_request()
9
        session.after_url = str('%s?%s' % (request.get_url(), request.get_query()))
10
        # TODO : SAML2
11
        login_url = '%s/liberty/larpe/login' % request.environ['SCRIPT_NAME']
12
        redirect(login_url)
larpe/branches/idwsf/larpe/federations.py
1
from storage import StorableObject
2

  
3
class Federation(StorableObject):
4
    _names = 'federations'
5

  
6
    username = None
7
    password = None
8
    host_id = None
9
    name_identifiers = None
10
    cookies = None
11
    select_fields = {}
12

  
13
    def __init__(self, username, password, host_id, name_identifier, cookies=None, select={}):
14
        StorableObject.__init__(self)
15
        self.username = username
16
        self.password = password
17
        self.host_id = host_id
18
        self.name_identifiers = [ name_identifier ]
19
        self.cookies = cookies
20
        self.select_fields = select
21

  
22
#     def add_name_identifier(self, name_identifier):
23
#         self.name_identifiers.append(name_identifier)
24

  
25
    def remove_name_identifier(self, name_identifier):
26
        self.name_identifiers.remove(name_identifier)
27
        if not self.name_identifiers:
28
            self.remove_self()
29

  
30
    def set_cookies(self, cookies):
31
        self.cookies = cookies
32

  
33
    def __str__(self):
34
        return 'Federation username : %s, name identifiers : %s, cookies : %s' \
35
                % (self.username, self.name_identifiers, self.cookies)
larpe/branches/idwsf/larpe/field_prefill.py
1
from storage import StorableObject
2

  
3
class FieldPrefill(StorableObject):
4
    _names = 'field_prefill'
5

  
6
    form_id = 0
7
    name = None
8
    xpath = None
9
    number = 1
10
    raw_xml = False
11
    regexp_match = None
12
    regexp_replacing = None
13
    select_options = {}
larpe/branches/idwsf/larpe/filter/larpe-filter.py
1
import os
2
import re
3

  
4
from mod_python import apache
5

  
6
#import larpe.hosts
7

  
8
app_dir = '/var/lib/larpe'
9

  
10
def outputfilter(filter):
11
    # Only filter html code
12
    if filter.req.content_type is not None:
13
        is_html = re.search('text/html', filter.req.content_type)
14
    if filter.req.content_type is not None and not is_html:
15
        filter.pass_on()
16
    else:
17
        if not hasattr(filter.req, 'temp_doc'): # the start
18
            filter.req.temp_doc = [] # create new attribute to hold document
19
            # If content-length ended up wrong, Gecko browsers truncated data, so
20
            if 'Content-Length' in filter.req.headers_out:
21
                del filter.req.headers_out['Content-Length']
22

  
23
#        filter.write(filter.req.headers_in['Cookie'])
24
#        delete_cookies(filter)
25
        #filter.req.headers_out['Set-Cookie'] = 'dc_admin="deleted"; max-age=0; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/'
26
        
27
        temp_doc = filter.req.temp_doc
28
        s = filter.read()
29
        while s: # could get '' at any point, but only get None at end
30
            temp_doc.append(s)
31
            s = filter.read()
32

  
33
        if s is None: # the end
34
            page = ''.join(temp_doc)
35
            
36
            page = filter_dispatch(filter, page)
37
            
38
            filter.write(page)
39
            filter.close()
40

  
41
def filter_dispatch(filter, page):
42
#    host = get_host_from_url(filter)
43
#    if host is None:
44
#        apache.log_error('Host not found')
45
#        return page
46
    try:
47
#        function_name = 'filter_' + host.label.lowercase()
48
        host_name = filter.req.hostname.split('.')[-3]
49
        function_name = 'filter_' + host_name
50
        return eval(function_name + '(filter, page)')
51
    except:
52
        return page
53
#        return filter_default(filter, page)
54

  
55

  
56
def filter_default(filter, page):
57
#    host = get_host_from_url(filter)
58
#    if host is None:
59
#        apache.log_error('Host not found')
60
#        return page
61
#    if host.auth_url is not None or host.auth_form is None:
62
#        return page
63
    form = find_auth_form(page)
64
    if form is not None:
65
        try:
66
            host_name = filter.req.hostname.split('.')[-3]
67
            return page.replace(form, """
68
                              <form method="post" action="/liberty/%s/login">
69
                                <input type="submit" value="Connexion" />
70
                              </form>""" % host_name)
71
        except:
72
            pass
73
    return page
74

  
75
def find_auth_form(page):
76
    regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
77
    found_forms = regexp.findall(page)
78

  
79
    for found_form in found_forms:
80
        regexp = re.compile("""<input[^>]*?type="password"[^>]*?>""", re.DOTALL | re.IGNORECASE)
81
        if regexp.search(found_form) is not None:
82
            return found_form
83
    return None
84

  
85
#def get_host_from_url(filter):
86
#    try:
87
#        return list(Host.select(lambda x: x.reversed_hostname == filter.req.hostname \
88
#                                          and x.reversed_directory == get_proxied_site_name(filter)))[0]
89
#    except:
90
#        return None
91

  
92

  
93
def filter_linuxfr(filter, page):
94
    str_to_replace = re.compile(str('<form method="post" action="/login.html" id="formulaire">.*?</form>'), re.DOTALL)
95
    return str_to_replace.sub(str(r"""<form method="post" action="/liberty/linuxfr/login" id="formulaire">
96
                                <div style="text-align: center; font-size: 13px;" class="loginbox">
97
                                <input type="submit" value="Connexion" />
98
                                <br />
99
                                <a href="/user_new.html">Cr&eacute;er un compte</a>
100
                                </div>
101
                                </form>"""
102
                                ), page)
103

  
104
def filter_dotclear(filter, page):
105
    if filter.req.uri == '/dot/ecrire/redac_list.php':
106
        str_to_replace = re.compile(str('(\[[^\?]+\?id=)([^"]+)(">[^\]]*)\]'), re.DOTALL)
107
        return str_to_replace.sub(str(r'\1\2\3 - <a href="/liberty/dot/admin_token?id=\2">token</a> ]'), page)
108
    if filter.req.uri == '/dot/ecrire/redacteur.php':
109
        str_to_replace = re.compile(str('(<form action=")redacteur.php'))
110
        page = str_to_replace.sub(str(r'\1/liberty/dot/admin_new_user'), page)
111
        str_to_replace = re.compile(str('<p class="field"><label class="float" for="user_pwd">.*?</p>'), re.DOTALL)
112
        return str_to_replace.sub(r'', page)
113
    return page
114

  
115
def filter_concerto(filter, page):
116
    str_to_replace = re.compile(str('<form action="login.do" method="post">.*?</form>'), re.DOTALL)
117
    return str_to_replace.sub(str(r"""<form method="post" action="/liberty/concerto/login" id="formulaire">
118
                                <div style="text-align: center; font-size: 13px;" class="loginbox">
119
                                <input type="submit" value="Connexion" />
120
                                </div>
121
                                </form>"""
122
                                ), page)
123

  
124
def get_abs_path(s):
125
    if not s:
126
        return s
127
    if s[0] == '/':
128
        return s
129
    return os.path.join(app_dir, s)
130
    
131
def get_proxied_site_path(filter):
132
    proxy_domain_name = filter.req.hostname
133
    proxied_site_dir = get_proxied_site_name(filter)
134
    return get_abs_path(os.path.join('sp', proxy_domain_name, proxied_site_dir))
135

  
136
def get_proxied_site_name(filter):
137
    uri_tokens = filter.req.uri.split('/')
138
    if uri_tokens[1] != 'liberty':
139
        return uri_tokens[1]
140
    return uri_tokens[2]
141
    
142
def delete_cookies(filter):
143
    success = False
144
    cookies_file_name = get_abs_path(os.path.join(get_proxied_site_path(filter), 'cookies_to_delete'))
145
    cookies_file = open(cookies_file_name, 'r')
146
    for cookie in cookies_file.read().split():
147
        if filter.req.headers_in.has_key('Cookie'):
148
            cookies_header = filter.req.headers_in['Cookie']
149
#            filter.req.temp_doc.append(filter.req.headers_in['Cookie'])
150
            #filter.req.temp_doc.append(cookie[len(cookie) -1:])
151
            cookies_match =  re.findall(cookie[:len(cookie) -1], cookies_header)
152
            if len(cookies_match) > 0:
153
                filter.req.temp_doc.append('User-Agent')
154
#                filter.req.temp_doc.append('%s="deleted"; max-age=0; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/' % cookie.split('=')[0])
155
                filter.req.headers_out['Set-Cookie'] = '%s="deleted"; max-age=0; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/' % cookie.split('=')[0]
156
#                cookies_file.close()
157
#                cookies_file = open(cookies_file_name, 'w')
158
                break
159
#            else:
160
#                filter.req.temp_doc.append('dommage')
161
    cookies_file.close()
162
#    if success:
163
#        cookies_file = open(cookies_file_name, 'w')
164
#        cookies_file.close()
larpe/branches/idwsf/larpe/form_prefill.py
1
from storage import StorableObject
2

  
3
class FormPrefill(StorableObject):
4
    _names = 'form_prefill'
5

  
6
    host_id = 0
7
    name = None
8
    url = None
9
    profile = None
10
    prefix = None
larpe/branches/idwsf/larpe/hosts.py
1
import os
2
from shutil import rmtree
3

  
4
from quixote import get_request
5

  
6
import misc
7
from storage import StorableObject
8
from Defaults import APP_DIR
9

  
10
class Host(StorableObject):
11
    _names = 'hosts'
12

  
13
    # Main settings
14
    label = None
15
    name = None
16
    orig_site = None
17
    new_url = None
18
    scheme = None
19
    auth_url = None
20
    auth_form_places = 'form_once'
21
    auth_form_page_url = None
22
    auth_form = None
23
    auth_form_url = None
24
    logout_url = None
25
    reversed_hostname = None
26
    reversed_directory = None
27
    organization_name = None
28
    use_ssl = False
29
    private_key = None
30
    public_key = None
31
    site_dir = None
32

  
33
    # Auto detected settings
34
    auth_mode = 'form'
35
    auth_form_action = None
36
    auth_check_url = None
37
    login_field_name = None
38
    password_field_name = None
39
    select_fields = {}
40
    post_parameters = {}
41
    http_headers = {}
42

  
43
    # Advanced settings
44
    return_url = '/'
45
    root_url = '/'
46
    auth_system = 'password'
47
    auth_match_text = ''
48
    send_hidden_fields = True
49
    initiate_sso_url = None
50
    redirect_root_to_login = False
51

  
52
    # Other attributes
53
    provider_id = None
54
    # Default value that indicates the proxy (if configured) is not disabled for this host yet
55
    use_proxy = True
56

  
57
    valid_username = None
58
    valid_password = None
59

  
60
    # Plugins
61
    # If name is set to None, use the default site authentication class
62
    site_authentication_plugin = None
63

  
64
    def get_host_from_url(cls):
65
        try:
66
            host = list(Host.select(lambda x: x.name == misc.get_proxied_site_name()))[0]
67
            if hasattr(host, 'site_authentication_instance'):
68
                del host.site_authentication_instance
69
            return list(Host.select(lambda x: x.name == misc.get_proxied_site_name()))[0]
70
        except Exception, e:
71
            return None
72
    get_host_from_url = classmethod(get_host_from_url)
73

  
74
    def get_host_with_provider_id(cls, provider_id):
75
        try:
76
            return list(Host.select(lambda x: x.provider_id == provider_id))[0]
77
        except:
78
            return None
79
    get_host_with_provider_id = classmethod(get_host_with_provider_id)
80

  
81
    def get_root_url(self):
82
        if self.root_url.startswith('/'):
83
            if self.reversed_directory:
84
                return '%s/%s%s' % (get_request().environ['SCRIPT_NAME'], self.reversed_directory, self.root_url)
85
            else:
86
                return '%s%s' % (get_request().environ['SCRIPT_NAME'], self.root_url)
87
        # In this case, must be a full url
88
        return self.root_url
89

  
90
    def get_return_url(self):
91
        if self.return_url.startswith('/'):
92
            if self.reversed_directory:
93
                return '%s/%s%s' % (get_request().environ['SCRIPT_NAME'], self.reversed_directory, self.return_url)
94
            else:
95
                return '%s%s' % (get_request().environ['SCRIPT_NAME'], self.return_url)
96
        # In this case, must be a full url
97
        return self.return_url
98

  
99
    def __cmp__(self, other):
100
        hostname_cmp = cmp(self.reversed_hostname, other.reversed_hostname)
101
        if hostname_cmp != 0:
102
            return hostname_cmp
103
        return cmp(self.reversed_directory, other.reversed_directory)
104

  
105
    def remove_object(cls, id):
106
        raise NotImplementedError()
107
    remove_object = classmethod(remove_object)
108

  
109
    def remove_self(self):
110
        # Main configuration file
111
        StorableObject.remove_self(self)
112
        # Other generated files
113
        if self.site_dir and os.path.exists(self.site_dir):
114
            rmtree(self.site_dir, ignore_errors=1)
115
            # Also remove hostname directory if empty (meaning there was no other subdirectory for this hostname)
116
            try:
117
                os.rmdir('/'.join(self.site_dir.split('/')[:-1]))
118
            except OSError:
119
                pass
120
        # Virtual host directory
121
        if self.reversed_hostname:
122
            dir = os.path.join(APP_DIR, self.reversed_hostname)
123
            if os.path.exists(dir):
124
                rmtree(dir, ignore_errors=1)
125

  
larpe/branches/idwsf/larpe/idwsf2.ptl
1
import os
2
import sys
3
import re
4

  
5
try:
6
    import lasso
7
except ImportError:
8
    print >> sys.stderr, 'Missing Lasso module, IdWsf 2.0 support disabled'
9
else:
10
    if not lasso.WSF_SUPPORT:
11
        print >> sys.stderr, 'Found Lasso module, but IdWsf 2.0 support not enabled'
12

  
13
from quixote import get_publisher, get_session, get_request, get_response, redirect
14
from quixote.directory import Directory
15

  
16
from qommon.liberty import SOAPException, soap_call
17
from qommon import template
18
from qommon.misc import http_get_page
19

  
20
import misc
21
from form_prefill import FormPrefill
22
from field_prefill import FieldPrefill
23

  
24
def cleanup_html_value(value):
25
    # Ensure the field value can be properly integrated in HTML code
26
    value = value.replace('"', "'")
27
    # Conversion to iso-8859-1
28
    try:
29
        value = unicode(value, 'utf-8').encode('iso-8859-1')
30
    except UnicodeEncodeError:
31
        return None
32

  
33
class IdWsf2(Directory):
34
    _q_exports = []
35

  
36
    def _q_lookup(self, component):
37
        if not hasattr(get_session(), 'prefill_form'):
38
            get_session().prefill_form = component
39
            get_session().after_url = get_request().get_url()
40
            if get_request().get_query():
41
                get_session().after_url += '?' + get_request().get_query()
42
            return redirect('../saml/login')
43
        else:
44
            prefill_form = FormPrefill.get(get_session().prefill_form)
45
            del get_session().prefill_form
46
            if prefill_form:
47
                try:
48
                    response, status, page, auth_header = http_get_page(prefill_form.url)
49
                except:
50
                    return template.error_page(_('Failed connecting to the original site.'))
51
                try:
52
                    fields = self.do_prefill_form(prefill_form)
53
                    if not fields:
54
                        raise lasso.Error
55
                    for key, value in get_request().get_fields().iteritems():
56
                        value = cleanup_html_value(value)
57
                        if value:
58
                            fields[key] = value
59
                except lasso.Error:
60
                    return page + '<script type="text/javascript">alert("%s")</script>' % \
61
                         _('Failed getting attributes from the attribute provider.')
62
                except:
63
                    return page + '<script type="text/javascript">alert("%s")</script>' % \
64
                         _('Failed getting attributes for an unknown reason.')
65

  
66
                return self.send_prefilled_form(prefill_form, page, fields)
67

  
68
    def do_prefill_form(self, prefill_form):
69
        server = misc.get_lasso_server(protocol = 'saml2')
70
        disco = lasso.IdWsf2Discovery(server)
71
        if not get_session().lasso_session_dumps or not get_session().lasso_session_dumps[server.providerId]:
72
            return None
73
        disco.setSessionFromDump(get_session().lasso_session_dumps[server.providerId])
74

  
75
        disco.initQuery()
76
        disco.addRequestedServiceType(prefill_form.profile)
77
        disco.buildRequestMsg()
78

  
79
        try:
80
            soap_answer = soap_call(disco.msgUrl, disco.msgBody)
81
        except SOAPException:
82
            return None
83
        disco.processQueryResponseMsg(soap_answer)
84

  
85
        service = disco.getService()
86
        lasso.registerIdWsf2DstService(prefill_form.prefix, prefill_form.profile)
87

  
88
        service.initQuery()
89

  
90
        fields = FieldPrefill.select(lambda x: x.form_id == prefill_form.id)
91
        for field in fields:
92
            if field.xpath and field.name:
93
                service.addQueryItem(field.xpath, field.name)
94

  
95
        service.buildRequestMsg()
96

  
97
        try:
98
            soap_answer = soap_call(service.msgUrl, service.msgBody)
99
        except SOAPException:
100
            return None
101
        service.processQueryResponseMsg(soap_answer)
102

  
103
        fields_dict = {}
104
        for field in fields:
105
            if not field.xpath or not field.name:
106
                continue
107
            if field.number > 0:
108
                number = field.number -1
109
            try:
110
                if field.raw_xml:
111
                    value = service.getAttributeNodes(field.name)[number]
112
                else:
113
                    value = service.getAttributeStrings(field.name)[number]
114
            except (IndexError, TypeError):
115
                value = ''
116
                # Log
117
            if value:
118
                # Regexp transformation
119
                if field.regexp_match:
120
                    value = re.sub(field.regexp_match, field.regexp_replacing, value)
121
                value = cleanup_html_value(value)
122
                # Conversion of select field options
123
                if field.select_options:
124
                    try:
125
                        value = field.select_options[value]
126
                    except (IndexError, KeyError):
127
                        pass
128
                if not value:
129
                    continue
130
            fields_dict[field.name] = value
131

  
132
        return fields_dict
133

  
134
    def send_prefilled_form(self, prefill_form, page, fields):
135
        for field_name, new_value in fields.iteritems():
136
            # Input
137
            regex = re.compile('(.*)(<input[^>]*? id="%s".*?>)(.*)' % field_name,
138
                               re.DOTALL | re.IGNORECASE)
139
            match = regex.match(page)
140
            if not match:
141
                regex = re.compile('(.*)(<input[^>]*? name="%s".*?>)(.*)' % field_name,
142
                                   re.DOTALL | re.IGNORECASE)
143
                match = regex.match(page)
144
            if match:
145
                before, input_field, after = match.groups()
146
                if 'value="' in input_field.lower():
147
                    regex_sub = re.compile('value=".*?"', re.DOTALL | re.IGNORECASE)
148
                    input_field = regex_sub.sub('value="%s"' % new_value, input_field)
149
                else:
150
                    input_field = input_field.replace('<input', '<input value="%s"' % new_value)
151
                page = ''.join([before, input_field, after])
152
                continue
153

  
154
            # Textarea
155
            regex = re.compile('(.*<textarea[^>]*? id="%s".*?>)[^<]*(</textarea>.*)' % field_name,
156
                               re.DOTALL | re.IGNORECASE)
157
            match = regex.match(page)
158
            if not match:
159
                regex = re.compile('(.*<textarea[^>]*? name="%s".*?>)[^<]*(</textarea>.*)' % field_name,
160
                                   re.DOTALL | re.IGNORECASE)
161
                match = regex.match(page)
162
            if match:
163
                before, after = match.groups()
164
                page = ''.join([before, new_value, after])
165
                continue
166

  
167
            # Select
168
            regex = re.compile('(.*<select[^>]*? id="%s".*?>)(.*?)(</select>.*)' % field_name,
169
                               re.DOTALL | re.IGNORECASE)
170
            match = regex.match(page)
171
            if not match:
172
                regex = re.compile('(.*<select[^>]*? name="%s".*?>)(.*?)(</select>.*)' % field_name,
173
                                   re.DOTALL | re.IGNORECASE)
174
                match = regex.match(page)
175
            if match:
176
                before, options, after = match.groups()
177
                # If the option to select is found, first unselect the previoulsy selected one
178
                regex2 = re.compile('(.*<option[^>]*? value="%s".*?)(>[^<]*</option>.*)' % new_value,
179
                               re.DOTALL | re.IGNORECASE)
180
                match2 = regex2.match(options)
181
                if match2:
182
                    before2, after2 = match2.groups()
183
                    regex3 = re.compile('(.*<option[^>]*?)( selected(="selected")?)(.*?>[^<]*</option>.*)',
184
                               re.DOTALL | re.IGNORECASE)
185
                    match3 = regex3.match(options)
186
                    if match3:
187
                        before3, selected, selected_value, after3 = match3.groups()
188
                        options = ''.join([before3, after3])
189
                regex2 = re.compile('(.*<option[^>]*? value="%s".*?)(>[^<]*</option>.*)' % new_value,
190
                    re.DOTALL | re.IGNORECASE)
191
                match2 = regex2.match(options)
192
                if match2:
193
                    before2, after2 = match2.groups()
194
                    options = ''.join([before2, ' selected="selected"', after2])
195

  
196
                page = ''.join([before, options, after])
197

  
198
        return page
199

  
larpe/branches/idwsf/larpe/liberty.ptl
1
import libxml2
2
import urllib
3
import urlparse
4
import httplib
5
import re
6
import os
7

  
8
from quixote import get_field, get_request, get_response, get_session, get_session_manager, redirect
9
from quixote.directory import Directory
10
from quixote.http_request import parse_header
11

  
12
import lasso
13

  
14
from qommon import get_logger
15
from qommon.form import *
16
from qommon.template import *
17

  
18
import misc
19
import storage
20
from users import User
21
from hosts import Host
22
from federations import Federation
23
import site_authentication
24

  
25
class Liberty(Directory):
26
    _q_exports = ['', 'login', 'assertionConsumer', 'soapEndpoint',
27
            'singleLogout', 'singleLogoutReturn',
28
            'federationTermination', 'federationTerminationReturn',
29
            ('metadata.xml', 'metadata'), 'public_key', 'local_auth']
30

  
31
    def perform_login(self, idp = None):
32
        server = misc.get_lasso_server()
33
        login = lasso.Login(server)
34
        login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
35
        login.request.nameIdPolicy = 'federated'
36
        login.request.forceAuthn = False
37
        login.request.isPassive = False
38
        login.request.consent = 'urn:liberty:consent:obtained'
39
        login.buildAuthnRequestMsg()
40
        return redirect(login.msgUrl)
41

  
42
    def assertionConsumer(self):
43
        server = misc.get_lasso_server()
44
        if not server:
45
            return error_page(_('Liberty support is not yet configured'))
46
        login = lasso.Login(server)
47
        request = get_request()
48
        if request.get_method() == 'GET' or get_field('LAREQ'):
49
            if request.get_method() == 'GET':
50
                login.initRequest(request.get_query(), lasso.HTTP_METHOD_REDIRECT)
51
            else:
52
                login.initRequest(get_field('LAREQ'), lasso.HTTP_METHOD_POST)
53

  
54
            login.buildRequestMsg()
55
            try:
56
                soap_answer = soap_call(login.msgUrl, login.msgBody)
57
            except SOAPException:
58
                return error_page(_('Failure to communicate with identity provider'))
59
            try:
60
                login.processResponseMsg(soap_answer)
61
            except lasso.Error, error:
62
                if error[0] == lasso.LOGIN_ERROR_STATUS_NOT_SUCCESS:
63
                    return error_page(_('Unknown authentication failure'))
64
                if hasattr(lasso, 'LOGIN_ERROR_UNKNOWN_PRINCIPAL'):
65
                    if error[0] == lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
66
                        return error_page(_('Authentication failure; unknown principal'))
67
                return error_page(_("Identity Provider didn't accept artifact transaction."))
68
        else:
69
            login.processAuthnResponseMsg(get_field('LARES'))
70
        login.acceptSso()
71
        session = get_session()
72
        if login.isSessionDirty:
73
            if login.session:
74
                session.lasso_session_dumps[server.providerId] = login.session.dump()
75
            else:
76
                session.lasso_session_dumps[server.providerId] = None
77

  
78
        # Look for an existing user
79
        user = self.lookup_user(session, login)
80

  
81
        # Check if it is for Larpe administration or token
82
        host = Host.get_host_from_url()
83
        if host is None:
84
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
85
        if host.name == 'larpe':
86
            session.name_identifier = login.nameIdentifier.content
87
            session.lasso_dump = login.identity.dump()
88
            session.provider_id = server.providerId
89

  
90
            if user:
91
                session.set_user(user.id, server.providerId)
92
                if hasattr(session, 'after_url') and session.after_url is not None and \
93
                        session.after_url.find('admin') != -1:
94
                    return redirect(session.after_url)
95
            else:
96
                if not user or not user.is_admin:
97
                    if hasattr(session, 'after_url') and session.after_url is not None \
98
                            and session.after_url.find('token') != -1:
99
                        return redirect(session.after_url)
100
                    return redirect('%s/token' % get_request().environ['SCRIPT_NAME'])
101
            return redirect('%s/admin/' % get_request().environ['SCRIPT_NAME'])
102

  
103

  
104
        # Set session user
105
        if not user:
106
            user = User()
107
        user.name_identifiers = [ login.nameIdentifier.content ]
108
        user.lasso_dumps = [ login.identity.dump() ]
109
        user.store()    
110
        session.set_user(user.id, server.providerId)
111
                       
112
#        elif request.user is not None:
113
#            user = request.user
114
#            session.set_user(user.id)
115
#        else:
116
#            session.set_user('anonymous-%s' % login.nameIdentifier.content)
117
#            user = session.get_user(login.nameIdentifier.content)
118
        
119
#         if not user.name_identifiers.has_key(server.providerId):
120
#             user.name_identifiers[server.providerId] = [ login.nameIdentifier.content ]
121
#         elif not login.nameIdentifier.content in user.name_identifiers[server.providerId]:
122
#             user.name_identifiers[server.providerId].append(login.nameIdentifier.content)
123
#         user.lasso_dumps[server.providerId] = login.identity.dump()
124
#         user.store()
125
#         request.user = user
126

  
127
        federations = Federation.select(lambda x: host.id == x.host_id \
128
                                        and user.name_identifiers[0] in x.name_identifiers)
129

  
130
        if federations:
131
            return site_authentication.get_site_authentication(host).sso_local_login(federations[0])
132
        else:
133
            if hasattr(session, 'token'):
134
                # FIXME : this method doesn't exist anymore
135
                self.federate_token(session.token)
136
            else:
137
                response = get_response()
138
                if session.after_url:
139
                    after_url = session.after_url
140
                    session.after_url = None
141
                    return redirect(after_url)
142
                response.set_status(303)
143
                response.headers['location'] = urlparse.urljoin(request.get_url(), str('local_auth'))
144
                response.content_type = 'text/plain'
145
                return 'Your browser should redirect you'
146

  
147
    def lookup_user(self, session, login): 
148
        found_users = list(User.select(lambda x: login.nameIdentifier.content in x.name_identifiers))
149
        if found_users:
150
            return found_users[0]
151
        return None
152

  
153
    def singleLogout(self):
154
        request = get_request()
155
        logout = lasso.Logout(misc.get_lasso_server())
156
        if lasso.isLibertyQuery(request.get_query()):
157
            try:
158
                logout.processRequestMsg(request.get_query())
159
            except lasso.Error, error:
160
                if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
161
                    return error_page(_('Failed to check single logout request signature.'))
162
                raise
163
            session = get_session()
164
            if not session.id:
165
  	            # session has not been found, this may be because the user has
166
  	            # its browser configured so that cookies are not sent for
167
  	            # remote queries and IdP is using image-based SLO.
168
  	            # so we look up a session with the appropriate name identifier
169
                for session in get_session_manager().values():
170
                    # This block differs from qommon
171
                    user = session.get_user(logout.server.providerId)
172
                    if user and logout.nameIdentifier.content in user.name_identifiers:
173
                        break
174
                else:
175
                    session = get_session()
176
            return self.slo_idp(logout, session)
177
        else:
178
            return self.slo_sp(logout, get_session())
179

  
180
    def singleLogoutReturn(self):
181
        logout = lasso.Logout(misc.get_lasso_server())
182
        host = Host.get_host_from_url()
183
        if host is None:
184
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
185

  
186
        try:
187
            logout.processResponseMsg(get_request().get_query())
188
        except lasso.Error, error:
189
            if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
190
                raise AccessError()
191
            if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
192
                return error_page(_('Failed to check single logout request signature.'))
193
            if hasattr(lasso, 'LOGOUT_ERROR_REQUEST_DENIED') and \
194
                    error[0] == lasso.LOGOUT_ERROR_REQUEST_DENIED:
195
                return redirect(host.get_root_url()) # ignore silently
196
            elif error[0] == lasso.ERROR_UNDEFINED:
197
                # XXX: unknown status; ignoring for now.
198
                return redirect(host.get_root_url()) # ignore silently
199
            raise
200
        return redirect(host.get_root_url())
201

  
202
    def slo_idp(self, logout, session):
203
        '''Single Logout initiated by IdP'''
204
        # This block differs from qommon
205
        if session.lasso_session_dumps.has_key(logout.server.providerId):
206
            logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
207
        user = session.get_user(logout.server.providerId)
208
        if user and user.lasso_dumps:
209
            logout.setIdentityFromDump(user.lasso_dumps[0])
210
        if user and logout.nameIdentifier.content not in user.name_identifiers:
211
            raise 'No appropriate name identifier in user (%s and %s)' % (
212
                    logout.nameIdentifier.content, user.name_identifiers)
213

  
214
        host = Host.get_host_with_provider_id(logout.server.providerId)
215
        if host is not None:
216
            site_authentication.get_site_authentication(host).local_logout(user=user)
217

  
218
        try:
219
            logout.validateRequest()
220
        except lasso.Error, error:
221
            if error[0] != lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
222
                raise
223
        else:
224
            get_session_manager().expire_session(logout.server.providerId)
225

  
226
        logout.buildResponseMsg()
227
        if logout.msgBody: # soap answer
228
            return logout.msgBody
229
        else:
230
            return redirect(logout.msgUrl)
231

  
232
    def slo_sp(self, logout, session):
233
        host = Host.get_host_from_url()
234
        if host is None:
235
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
236

  
237
        if not session.id or not session.users.has_key(logout.server.providerId) \
238
                or not session.lasso_session_dumps.has_key(logout.server.providerId):
239
            get_session_manager().expire_session(logout.server.providerId)
240
            return redirect(host.get_root_url())
241

  
242
        logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
243
        user = session.get_user(logout.server.providerId)
244
        site_authentication.get_site_authentication(host).local_logout(user=user)
245
        if user and user.lasso_dumps:
246
            logout.setIdentityFromDump(user.lasso_dumps[0])
247

  
248
        return self.slo_sp_redirect(logout, host)
249

  
250
    def slo_sp_redirect(self, logout, host):
251
        try:
252
            logout.initRequest(None, lasso.HTTP_METHOD_REDIRECT)
253
        except lasso.Error, error:
254
            if error[0] == lasso.PROFILE_ERROR_NAME_IDENTIFIER_NOT_FOUND:
255
                get_session_manager().expire_session() 
256
                return redirect(host.get_root_url())
257
            raise
258
        logout.buildRequestMsg()
259
        get_session_manager().expire_session(logout.server.providerId)
260
        return redirect(logout.msgUrl)
261

  
262
    def soapEndpoint(self):
263
        request = get_request()
264
        ctype = request.environ.get('CONTENT_TYPE')
265
        if not ctype:
266
            return
267

  
268
        ctype, ctype_params = parse_header(ctype)
269
        if ctype != 'text/xml':
270
            return
271

  
272
        response = get_response()
273
        response.set_content_type('text/xml')
274

  
275
        length = int(request.environ.get('CONTENT_LENGTH'))
276
        soap_message = request.stdin.read(length)
277

  
278
        request_type = lasso.getRequestTypeFromSoapMsg(soap_message) 
279

  
280
        if request_type == lasso.REQUEST_TYPE_LOGOUT:
281
            logout = lasso.Logout(misc.get_lasso_server())
282
            logout.processRequestMsg(soap_message)
283
            name_identifier = logout.nameIdentifier.content
284
            for session in get_session_manager().values():
285
                user = session.get_user(logout.server.providerId)
286
                if user and logout.nameIdentifier.content in user.name_identifiers:
287
                    break
288
            else:
289
                session = None
290
            return self.slo_idp(logout, session)
291

  
292
        if request_type == lasso.REQUEST_TYPE_DEFEDERATION:
293
            defederation = lasso.Defederation(misc.get_lasso_server())
294
            defederation.processNotificationMsg(soap_message)
295
            for session in get_session_manager().values():
296
                user = session.get_user(defederation.server.providerId)
297
                if user and defederation.nameIdentifier.content in user.name_identifiers:
298
                    break
299
            else:
300
                session = None
301
            return self.fedterm(defederation, session)
302

  
303
    def federationTermination(self):
304
        request = get_request()
305
        if not lasso.isLibertyQuery(request.get_query()):
306
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
307
        
308
        defederation = lasso.Defederation(misc.get_lasso_server())
309
        defederation.processNotificationMsg(request.get_query())
310
        return self.fedterm(defederation, get_session())
311

  
312
    def fedterm(self, defederation, session):
313
        if session is not None:
314
            host = Host.get_host_with_provider_id(defederation.server.providerId)
315
            if host is not None:
316
                site_authentication.get_site_authentication(host).local_defederate(session, defederation.server.providerId)
317
            if session.lasso_session_dumps.has_key(defederation.server.providerId):
318
                defederation.setSessionFromDump(session.lasso_session_dumps[defederation.server.providerId])
319
            user = session.get_user(defederation.server.providerId)
320
            if user and user.lasso_dumps:
321
                defederation.setIdentityFromDump(user.lasso_dumps[0])
322
        else:
323
            user = None
324

  
325
        try:
326
            defederation.validateNotification()
327
        except lasso.Error, error:
328
            pass # ignore failure (?)
329
        else:
330
            if user:
331
                if not defederation.identity:
332
                    # if it was the last federation the whole identity dump collapsed
333
                    del user.lasso_dumps[0]
334
                else:
335
                    user.lasso_dumps[0] = defederation.identity.dump()
336
                user.store()
337

  
338
        if user and defederation.nameIdentifier.content:
339
            user.remove_name_identifier(defederation.server.providerId, defederation.nameIdentifier.content)
340
            user.store()
341

  
342
        if defederation.isSessionDirty and session is not None:
343
            if not defederation.session:
344
                del session.lasso_session_dumps[defederation.server.providerId]
345
            else:
346
                session.lasso_session_dumps[defederation.server.providerId] = defederation.session.dump()
347
            session.store()
348

  
349
        get_session_manager().expire_session(defederation.server.providerId)
350

  
351
        if defederation.msgUrl:
352
            return redirect(defederation.msgUrl)
353
        else:
354
            response = get_response()
355
            response.set_status(204)
356
            return ''
357

  
358
    def federationTerminationReturn(self):
359
        host = Host.get_host_from_url()
360
        if host is None:
361
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
362
        return redirect(host.get_return_url())
363

  
364
#     def defederate(self, idp = None):
365
#         session = get_session()
366
#         user = get_request().user
367

  
368
#         server = misc.get_lasso_server()
369

  
370
#         self.local_defederate(session, server.providerId)
371
#         
372
#         defederation = lasso.Defederation(server)
373
#         defederation.setSessionFromDump(session.lasso_session_dumps[defederation.server.providerId])
374
#         if user and user.lasso_dumps.has_key(defederation.server.providerId):
375
#             defederation.setIdentityFromDump(user.lasso_dumps[defederation.server.providerId])
376

  
377
#         defederation.initNotification(idp, lasso.HTTP_METHOD_SOAP);
378
#         defederation.buildNotificationMsg();
379

  
380
#         try:
381
#             soap_call(defederation.msgUrl, defederation.msgBody);
382
#         except SOAPException:
383
#             return error_page(_('Failure to communicate with identity provider'))
384

  
385
#         rootUrl = '/' + misc.get_proxied_site_name() + '/'
386
#         return redirect(rootUrl)
387

  
388
    def local_auth(self):
389
        host = Host.get_host_from_url()
390
        if host is None:
391
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
392
        return site_authentication.get_site_authentication(host).local_auth
393
    local_auth = property(local_auth)
394

  
395
    def metadata(self):
396
        host = Host.get_host_from_url()
397
        if host is None:
398
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
399
        get_response().set_content_type('text/xml', 'utf-8')
400
        metadata = unicode(open(host.metadata).read(), 'utf-8')
401
        return metadata
402

  
403
    def public_key(self):
404
        host = Host.get_host_from_url()
405
        if host is None:
406
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
407
        get_response().set_content_type('text/plain')
408
        public_key = open(host.public_key).read()
409
        return public_key
410

  
411

  
412
class SOAPException(Exception):
413
    pass
414

  
415

  
416
def soap_call(url, msg):
417
    if url.startswith('http://'):
418
        host, query = urllib.splithost(url[5:])
419
        conn = httplib.HTTPConnection(host)
420
    else:
421
        host, query = urllib.splithost(url[6:])
422
        conn = httplib.HTTPSConnection(host)
423
    conn.request('POST', query, msg, {'Content-Type': 'text/xml'})
424
    response = conn.getresponse()
425
    data = response.read()
426
    conn.close()
427
    if response.status not in (200, 204): # 204 ok for federation termination
428
        get_logger().warn('SOAP error (%s) (on %s)' % (response.status, url))
429
        raise SOAPException()
430
    return data
larpe/branches/idwsf/larpe/liberty_root.ptl
1
from quixote.directory import Directory
2
from quixote import get_response
3

  
4
from liberty_site import LibertySite
5

  
6
class LibertyRootDirectory(Directory):
7

  
8
    def _q_lookup(self, component):
9
        return LibertySite(component)
larpe/branches/idwsf/larpe/liberty_site.ptl
1
import sys
2
import random
3

  
4
from quixote import get_publisher, get_response, redirect, get_request
5
from quixote.directory import Directory
6
from quixote.errors import TraversalError
7

  
8
import lasso
9

  
10
import admin
11
import liberty
12
import saml2
13
import idwsf2
14
import httplib
15
import urllib
16

  
17
from qommon.form import *
18
from qommon.misc import get_abs_path, get_current_protocol
19
from qommon import template, get_logger
20

  
21
import errors
22
import misc
23

  
24
from users import User
25
from hosts import Host
26

  
27
class LibertySite(Directory):
28

  
29
    _q_exports = ['', 'login', 'logout', 'liberty', 'saml', 'idwsf2']
30

  
31
    liberty = liberty.Liberty()
32
    saml = saml2.Saml2()
33
    idwsf2 = idwsf2.IdWsf2()
34

  
35
    def __init__(self, component):
36
        self.name = component
37

  
38
    def _q_index (self):
39
        raise errors.TraversalError()
40

  
41
    def login [html] (self):
42
        get_logger().info('login')
43
        get_publisher().reload_cfg()
44

  
45
        if not get_publisher().cfg.has_key('idp'):
46
            return template.error_page(_('SSO support is not yet configured'))
47
        else:
48
            server = misc.get_lasso_server('liberty')
49
            if server is not None:
50
                return self.liberty.perform_login()
51

  
52
            server = misc.get_lasso_server('saml2')
53
            if server is not None:
54
                return self.saml.perform_login()
55

  
56
            return template.error_page(_('SSO support is not yet configured'))
57

  
58
    def logout(self):
59
        get_logger().info('logout')
60
        session = get_session()
61
        if not session:
62
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
63

  
64
        if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
65
            return self.saml.slo_sp()
66
        else:
67
            return self.liberty.singleLogout()
68

  
larpe/branches/idwsf/larpe/misc.py
1
import re
2
import os
3

  
4
import lasso
5

  
6
from quixote import get_publisher, get_request
7

  
8
from qommon.misc import get_abs_path
9

  
10
from hosts import Host
11

  
12
def get_root_url():
13
    req = get_request()
14
    return '%s://%s%s' % (req.get_scheme(), req.get_server(), req.environ['SCRIPT_NAME'])
15

  
16
def get_proxied_site_path():
17
    host = Host.get_host_from_url()
18
    if host is None:
19
        return None
20
    return host.site_dir
21

  
22
def get_proxied_site_domain():
23
    return get_request().get_server().split(':')[0]
24

  
25
def get_proxied_site_name():
26
    nb_subdirs = get_request().environ['SCRIPT_NAME'].count('/')
27
    return get_request().get_path().split('/')[nb_subdirs + 2]
28

  
29
def get_identity_provider_config():
30
    get_publisher().reload_cfg()
31
    idps_dir = get_abs_path('idp')
32
    if get_publisher().cfg.has_key('idp'):
33
        idp_dir = os.path.join(idps_dir, get_publisher().cfg['idp'])
34

  
35
        metadata_path = os.path.join(idp_dir, 'metadata.xml')
36

  
37
        public_key_path = os.path.join(idp_dir, 'public_key')
38
        if not os.path.isfile(public_key_path):
39
            public_key_path = None
40

  
41
        ca_cert_chain_path = os.path.join(idp_dir, 'ca_cert_chain.pem')
42
        if not os.path.isfile(ca_cert_chain_path):
43
            ca_cert_chain_path = None
44

  
45
        return metadata_path, public_key_path, ca_cert_chain_path
46
    return None, None, None
47

  
48
def get_lasso_server(protocol='liberty'):
49
    proxied_site_path = get_proxied_site_path()
50
    if proxied_site_path is None:
51
        return None
52
    if protocol == 'liberty':
53
        server = lasso.Server(
54
            os.path.join(proxied_site_path, 'metadata.xml'),
55
            os.path.join(proxied_site_path, 'private_key.pem'),
56
            None, None)
57
    elif protocol == 'saml2':
58
        server = lasso.Server(
59
            os.path.join(proxied_site_path, 'saml2_metadata.xml'),
60
            os.path.join(proxied_site_path, 'private_key.pem'),
61
            None, None)
62
    else:
63
        raise 'XXX: unknown protocol'
64

  
65
    metadata_path, public_key_path, ca_cert_chain_path = get_identity_provider_config()
66
    if metadata_path:
67
        try:
68
            server.addProvider(
69
                    lasso.PROVIDER_ROLE_IDP,
70
                    metadata_path,
71
                    public_key_path,
72
                    ca_cert_chain_path)
73
        except lasso.Error, error:
74
            if error[0] == lasso.SERVER_ERROR_ADD_PROVIDER_PROTOCOL_MISMATCH:
75
                return None
76
            if error[0] == lasso.SERVER_ERROR_ADD_PROVIDER_FAILED:
77
                return None
78
            raise
79

  
80
    return server
81

  
82
def get_provider_label(provider):
83
    if not provider:
84
        return None
85
    if not hasattr(provider, str('getOrganization')):
86
        return provider.providerId
87

  
88
    organization = provider.getOrganization()
89
    if not organization:
90
        return provider.providerId
91

  
92
    name = re.findall("<OrganizationDisplayName.*>(.*?)</OrganizationDisplayName>", organization)
93
    if not name:
94
        name = re.findall("<OrganizationName.*>(.*?)</OrganizationName>", organization)
95
        if not name:
96
            return provider.providerId
97
    return name[0]
98

  
99
def get_current_protocol():
100
    metadata_path, public_key_path, ca_cert_chain_path = get_identity_provider_config()
101
    if not metadata_path:
102
        return None
103
    try:
104
        provider = lasso.Provider(lasso.PROVIDER_ROLE_IDP, metadata_path, public_key_path, None)
105
    except lasso.Error:
106
        return None
107
    else:
108
        return provider.getProtocolConformance()
109

  
larpe/branches/idwsf/larpe/plugins/site_authentication/agirhe.py
1
import re
2
import urllib
3

  
4
from quixote import get_request, get_response, get_session
5

  
6
from qommon.misc import http_post_request
7
from qommon import get_logger
8

  
9
from larpe.qommon.misc import http_get_page
10
import site_authentication
11

  
12
class AgirheSiteAuthentication(site_authentication.SiteAuthentication):
13
    plugin_name = 'agirhe'
14

  
15
    def auto_detect_site(cls, html_doc):
16
        if re.search("""<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/""", html_doc):
17
            return True
18
        return False
19
    auto_detect_site = classmethod(auto_detect_site)
20

  
21
    def local_auth_check_post(self, username, password, select={}, session_cookies=False):
22
        url = self.host.auth_check_url
23

  
24
        # Build request body
25
        body = '%s=%s&%s=%s' % (self.host.login_field_name, username, self.host.password_field_name, password)
26
        # Add select fields to the body
27
        for name, value in select.iteritems():
28
            body += '&%s=%s' % (name, value)
29

  
30
        # Get the authentication page
31
        try:
32
            response, status, page, auth_header = http_get_page(self.host.auth_form_url, use_proxy=self.host.use_proxy)
33
        except Exception, err:
34
            print err
35
            return None, None
36

  
37
        # Get current hidden fields everytime
38
        self.parse_forms(page)
39
        if self.host.auth_form is not None:
40
            input_fields = self.parse_input_fields()
41
            self.parse_other_fields(input_fields)
42

  
43
        # Add hidden fields to the body
44
        for key, value in self.host.other_fields.iteritems():
45
            value = urllib.quote_plus(value)
46
            body += '&%s=%s' % (key, value)
47

  
48
        # Build request HTTP headers
49
        headers = {'Content-Type': 'application/x-www-form-urlencoded',
50
                   'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
51
                   'X-Forwarded-Host': self.host.reversed_hostname}
52

  
53
        # Send request
54
        response, status, data, auth_headers = http_post_request(url, body, headers, self.host.use_proxy)
55

  
56
        cookies = response.getheader('Set-Cookie', None)
57
        self.host.cookies = []
58
        if cookies is not None:
59
            cookies_list = []
60
            cookies_set_list = []
61
            for cookie in cookies.split(', '):
62
                # Drop the path and other attributes
63
                cookie_only = cookie.split('; ')[0]
64
                regexp = re.compile('=')
65
                if regexp.search(cookie_only) is None:
66
                    continue
67
                # Split name and value
68
                cookie_split = cookie_only.split('=')
69
                cookie_name = cookie_split[0]
70
                cookie_value = cookie_split[1]
71
                cookies_list.append('%s=%s' % (cookie_name, cookie_value))
72
                set_cookie = '%s=%s; path=/' % (cookie_name, cookie_value)
73
                cookies_set_list.append(set_cookie)
74
                self.host.cookies.append(cookie_name)
75
            cookies_headers = '\r\nSet-Cookie: '.join(cookies_set_list)
76
            get_response().set_header('Set-Cookie', cookies_headers)
77
            self.host.store()
78
            get_session().cookies = '; '.join(cookies_list)
79
        else:
80
            get_logger().warn('No cookie from local authentication')
81

  
82
        return response.status, data
83

  
84
    # The 3 following functions have been copied from admin/hosts.ptl
85

  
86
    def parse_forms(self, page):
87
        '''Search for an authentication form'''
88
        # Get all forms
89
        regexp = re.compile("""<form.*?</form>""", re.DOTALL | re.IGNORECASE)
90
        found_forms = regexp.findall(page)
91
        if not found_forms:
92
            return
93
            #raise FormError, ('auth_check_url', '%s : %s' % (_('Failed to find any form'), self.host.auth_form_url))
94

  
95
        # Get the first form with a password field
96
        for found_form in found_forms:
97
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
98
            if regexp.search(found_form) is not None:
99
                self.host.auth_form = found_form
100
                break
101

  
102
    def parse_input_fields(self):
103
        '''Get all input fields'''
104
        regexp = re.compile("""<input[^>]*?>""", re.DOTALL | re.IGNORECASE)
105
        return regexp.findall(self.host.auth_form)
106

  
107
    def parse_other_fields(self, input_fields):
108
        '''Get the default value of all other fields'''
109
        self.host.other_fields = {}
110

  
111
        # Get hidden fields
112
        regexp = re.compile("""<input[^>]*?type=["']?hidden["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
113
        other_fields = regexp.findall(self.host.auth_form)
114

  
115
        # Only get first submit field
116
        regexp = re.compile("""<input[^>]*?type=["']?submit["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
117
        found = regexp.findall(self.host.auth_form)
118
        if found:
119
            if other_fields:
120
                other_fields.append(found[0])
121
            else:
122
                other_fields = found[0]
123

  
124
        for field in other_fields:
125
            try:
126
                regexp = re.compile("""name=["']?(.*?)["']?[\s/>]""", re.DOTALL | re.IGNORECASE)
127
                name = regexp.findall(field)[0]
128
                regexp = re.compile("""value=["'](.*?)["'][\s/>]""", re.DOTALL | re.IGNORECASE)
129
                value = regexp.findall(field)[0]
130
                self.host.other_fields[name] = value
131
                if not self.host.post_parameters.has_key(name):
132
                    self.host.post_parameters[name] = { 'enabled': True, 'value': value, 'immutable': False }
133
            except IndexError, e:
134
                continue
135

  
136
site_authentication.register_site_authentication_class(AgirheSiteAuthentication)
137

  
larpe/branches/idwsf/larpe/plugins/site_authentication/ciril_net_rh.py
1
import re
2

  
3
from quixote import get_request, get_response, get_session, redirect
4

  
5
from qommon.misc import http_post_request
6
from qommon import get_logger
7

  
8
import site_authentication
9

  
10
class CirilSiteAuthentication(site_authentication.SiteAuthentication):
11
    plugin_name = 'ciril'
12

  
13
    def auto_detect_site(cls, html_doc):
14
        if re.search("""<form name="myForm" id="myForm" method="post" target="choixAppli" action="/cgi-bin/acces.exe" """, html_doc):
15
            print 'ok'
16
            return True
17
        return False
18
    auto_detect_site = classmethod(auto_detect_site)
19

  
20
    def check_auth(self, status, data):
21
        success = False
22
        return_content = ''
23

  
24
        # If status is 500, fail without checking other criterias
25
        if status // 100 == 5:
26
            success = False
27
            return_content = redirect(self.host.get_return_url())
28

  
29
        regexp = re.compile("""javascript\:window\.open\('(/net_rh/accueil.php\?.*?)', '_blank'\)""", re.DOTALL | re.IGNORECASE)
30
        match = regexp.findall(data)
31
        if match:
32
            success = True
33
            return_content = redirect(match[0])
34

  
35
        return success, return_content
36

  
37
site_authentication.register_site_authentication_class(CirilSiteAuthentication)
38

  
larpe/branches/idwsf/larpe/plugins/site_authentication/concerto.py
1
import re
2

  
3
from quixote import get_request, get_response, get_session
4

  
5
from qommon.misc import http_post_request
6
from qommon import get_logger
7

  
8
import site_authentication
9

  
10
class ConcertoSiteAuthentication(site_authentication.SiteAuthentication):
11
    plugin_name = 'concerto'
12

  
13
    def auto_detect_site(cls, html_doc):
14
        if re.search("""<meta name="description" content="Page d'accueil du site Espace-Famille" />""", html_doc):
15
            return True
16
        return False
17
    auto_detect_site = classmethod(auto_detect_site)
18

  
19
    def local_auth_check_post(self, username, password, select={}, session_cookies=False):
20
        url = self.host.auth_check_url
21

  
22
        # Build request body
23
        body = '%s=%s&%s=%s' % (self.host.login_field_name, username, self.host.password_field_name, password)
24
        # Add select fields to the body
25
        for name, value in select.iteritems():
26
            body += '&%s=%s' % (name, value)
27
        # Add hidden fields to the body
28
        if self.host.send_hidden_fields:
29
            for key, value in self.host.other_fields.iteritems():
30
                body += '&%s=%s' % (key, value)
31

  
32
        # Build request HTTP headers
33
        headers = {'Content-Type': 'application/x-www-form-urlencoded',
34
                   'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
35
                   'X-Forwarded-Host': self.host.reversed_hostname}
36
        
37
        # Add session id cookie
38
        if session_cookies is True:
39
            for key, value in self.host.other_fields.iteritems():
40
                headers['Cookie'] = 'JSESSIONID=' + value
41

  
42
        # Send request
43
        response, status, data, auth_headers = http_post_request(url, body, headers, self.host.use_proxy)
44

  
45
        cookies = response.getheader('Set-Cookie', None)
46
        self.host.cookies = []
47
        new_session_id = None
48
        if cookies is not None:
49
            cookies_list = []
50
            cookies_set_list = []
51
            for cookie in cookies.split(', '):
52
                # Drop the path and other attributes
53
                cookie_only = cookie.split('; ')[0]
54
                regexp = re.compile('=')
55
                if regexp.search(cookie_only) is None:
56
                    continue
57
                # Split name and value
58
                cookie_split = cookie_only.split('=')
59
                cookie_name = cookie_split[0]
60
                cookie_value = cookie_split[1]
61
                if cookie_name == 'JSESSIONID':
62
                    new_session_id = cookie_value
63
                cookies_list.append('%s=%s' % (cookie_name, cookie_value))
64
                set_cookie = '%s=%s; path=/demo' % (cookie_name, cookie_value)
65
                cookies_set_list.append(set_cookie)
66
                self.host.cookies.append(cookie_name)
67
            cookies_headers = '\r\nSet-Cookie: '.join(cookies_set_list)
68
            get_response().set_header('Set-Cookie', cookies_headers)
69
            self.host.store()
70
            get_session().cookies = '; '.join(cookies_list)
71
        else:
72
            get_logger().warn('No cookie from local authentication')
73

  
74
        if session_cookies is False:
75
            # Change idSession hidden field with new session id
76
            self.host.other_fields['idSession'] = new_session_id
77
            # Retry the request with the new session id
78
            return self.local_auth_check_post(username, password, select, session_cookies=True)
79
        else:
80
            return response.status, data
81

  
82
site_authentication.register_site_authentication_class(ConcertoSiteAuthentication)
83

  
larpe/branches/idwsf/larpe/plugins/site_authentication/egroupware.py
1
import re
2
import urlparse
3

  
4
from quixote import get_request, get_response, get_session
5

  
6
from qommon.misc import http_post_request, http_get_page
7
from qommon import get_logger
8

  
9
import site_authentication
10

  
11
class EgroupwareSiteAuthentication(site_authentication.SiteAuthentication):
12
    plugin_name = 'egroupware'
13

  
14
    def auto_detect_site(cls, html_doc):
15
        if re.search("""<meta name="description" content="eGroupWare" />""", html_doc):
16
            return True
17
        return False
18
    auto_detect_site = classmethod(auto_detect_site)
19

  
20
    def local_auth_check_post(self, username, password, select={}):
21
        url = self.host.auth_check_url
22

  
23
        # Build request body
24
        body = '%s=%s&%s=%s' % (self.host.login_field_name, username, self.host.password_field_name, password)
25
        # Add select fields to the body
26
        for name, value in select.iteritems():
27
            body += '&%s=%s' % (name, value)
28
        # Add hidden fields to the body
29
        if self.host.send_hidden_fields:
30
            for key, value in self.host.other_fields.iteritems():
31
                body += '&%s=%s' % (key, value)
32

  
33
        # Build request HTTP headers
34
        # FIXME : Add back {'Host': hostname}
35
        headers = {'Content-Type': 'application/x-www-form-urlencoded',
36
                   'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
37
                   'X-Forwarded-Host': self.host.reversed_hostname}
38

  
39
        # Send request
40
        response, status, data, auth_headers = http_post_request(url, body, headers, self.host.use_proxy)
41

  
42
        # The specific code is these 2 lines and the called function
43
        if self.host.name.startswith('egroupware'):
44
            data = self.get_data_after_redirects(response, data)
45

  
46
        cookies = response.getheader('Set-Cookie', None)
47
        self.host.cookies = []
48
        if cookies is not None:
49
            cookies_list = []
50
            cookies_set_list = []
51
            for cookie in cookies.split(', '):
52
                # Drop the path and other attributes
53
                cookie_only = cookie.split('; ')[0]
54
                regexp = re.compile('=')
55
                if regexp.search(cookie_only) is None:
56
                    continue
57
                # Split name and value
58
                cookie_split = cookie_only.split('=')
59
                cookie_name = cookie_split[0]
60
                cookie_value = cookie_split[1]
61
                cookies_list.append('%s=%s' % (cookie_name, cookie_value))
62
                set_cookie = '%s=%s; path=/' % (cookie_name, cookie_value)
63
                cookies_set_list.append(set_cookie)
64
                self.host.cookies.append(cookie_name)
65
            cookies_headers = '\r\nSet-Cookie: '.join(cookies_set_list)
66
            get_response().set_header('Set-Cookie', cookies_headers)
67
            self.host.store()
68
            get_session().cookies = '; '.join(cookies_list)
69
        else:
70
            get_logger().warn('No cookie from local authentication')
71

  
72
        return response.status, data
73

  
74
    def get_data_after_redirects(self, response, data):
75
        status = response.status
76
        headers = {'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
77
                   'X-Forwarded-Host': self.host.reversed_hostname}
78
        while status == 302:
79
            location = response.getheader('Location', None)
80
            if location is not None:
81
                url_tokens = urlparse.urlparse(self.host.auth_check_url)
82
                url = '%s://%s%s'% (url_tokens[0], url_tokens[1], location)
83
                response, status, data, auth_headers = http_get_page(url, headers, self.host.use_proxy)
84
        return data
85

  
86
site_authentication.register_site_authentication_class(EgroupwareSiteAuthentication)
87

  
larpe/branches/idwsf/larpe/plugins/site_authentication/sympa.py
1
import re
2

  
3
from quixote import get_response, redirect
4
from quixote.html import htmltext
5

  
6
import site_authentication
7

  
8
class SympaSiteAuthentication(site_authentication.SiteAuthentication):
9
    plugin_name = 'sympa'
10

  
11
    def auto_detect_site(cls, html_doc):
12
        if re.search("""<FORM ACTION="/wwsympa.fcgi" METHOD=POST>""", html_doc):
13
            return True
14
        return False
15
    auto_detect_site = classmethod(auto_detect_site)
16

  
17
    def check_auth(self, status, data):
18
        success = False
19
        return_content = ''
20
        
21
        if self.host.auth_system == 'password':
22
            # If there is a password field, authentication probably failed
23
            regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
24
            if not regexp.findall(data):
25
                success = True
26
                # The specific part is only these 2 lines
27
                get_response().filter.update({'no_template': True})
28
                return_content = htmltext(data)
29
        elif self.host.auth_system == 'status':
30
            match_status = int(self.host.auth_match_status)
31
            if match_status == status:
32
                success = True
33
                return_content = redirect(self.host.return_url)
34
        elif self.host.auth_system == 'match_text':
35
            # If the auth_match_text is not matched, it means the authentication is successful
36
            regexp = re.compile(self.host.auth_match_text, re.DOTALL)
37
            if not regexp.findall(data):
38
                success = True
39
                return_content = redirect(self.host.get_return_url())
40

  
41
        return success, return_content
42

  
43
site_authentication.register_site_authentication_class(SympaSiteAuthentication)
larpe/branches/idwsf/larpe/publisher.py
1
import os
2
import cPickle
3

  
4
from quixote import get_response, get_session, get_request
5

  
6
from Defaults import *
7

  
8
from qommon.publisher import set_publisher_class, QommonPublisher
9
from qommon import template
10

  
11
from root import RootDirectory
12
from admin import RootDirectory as AdminRootDirectory
13

  
14
from sessions import StorageSessionManager
15
from users import User
16
from hosts import Host
17

  
18
class LarpePublisher(QommonPublisher):
19
    APP_NAME = 'larpe'
20
    APP_DIR = APP_DIR
21
    DATA_DIR = DATA_DIR
22
    ERROR_LOG = ERROR_LOG
23
    WEB_ROOT = '/larpe'
24

  
25
    supported_languages = ['fr']
26

  
27
    root_directory_class = RootDirectory
28
    admin_directory_class = AdminRootDirectory
29

  
30
    session_manager_class = StorageSessionManager
31
    user_class = User
32

  
33
    def get_web_root_url(self):
34
        return get_request().environ['SCRIPT_NAME'] + WEB_ROOT + '/'
35

  
36
    def _get_user_name(self, host):
37
        user = get_session().get_user(host.provider_id)
38
        if user and user.name:
39
            return user.name
40
        return None
41

  
42
    def filter_output(self, request, output):
43
        response = get_response()
44
        if response.content_type != 'text/html':
45
            return output
46
        if not hasattr(response, 'filter') or not response.filter:
47
            return output
48
        
49
        org_name = 'Larpe'
50
        user_name = None
51
        host = Host.get_host_from_url()
52
        if host is not None:
53
            org_name = host.organization_name
54
        #    user_name = self._get_user_name(host)
55
        return template.decorate(output, response)
56

  
57
    def set_app_dir(self, request):
58
        self.app_dir = os.path.join(self.APP_DIR, request.get_server().lower().split(':')[0])
59
        self.reload_cfg()
60
        if self.cfg.has_key('proxy_hostname'):
61
            self.app_dir = os.path.join(self.APP_DIR, self.cfg['proxy_hostname'])
62
        if self.app_dir is not None and not os.path.exists(self.app_dir):
63
            os.mkdir(self.app_dir)
64

  
65
    cfg = None
66
    def write_cfg(self, directory=None):
67
        s = cPickle.dumps(self.cfg)
68
        if directory is None:
69
            directory = self.app_dir
70
        filename = os.path.join(directory, 'config.pck')
71
        open(filename, 'w').write(s)
72

  
73

  
74
set_publisher_class(LarpePublisher)
75
LarpePublisher.register_extra_dir(os.path.join(os.path.dirname(__file__), 'plugins', 'site_authentication'))
76

  
larpe/branches/idwsf/larpe/root.ptl
1
import os
2
import httplib
3

  
4
import lasso
5

  
6
from quixote import get_request, get_response, get_session, redirect
7
from quixote.directory import Directory
8

  
9
from qommon.form import *
10
from qommon import template
11

  
12
import admin
13
import liberty_root
14
import errors
15

  
16
from hosts import Host
17
from users import User
18
from Defaults import WEB_ROOT
19

  
20
class RootDirectory(Directory):
21
    _q_exports = ['', 'admin', 'liberty', 'token']
22

  
23
    admin = admin.RootDirectory()
24
    liberty = liberty_root.LibertyRootDirectory()
25

  
26
    def _q_index [html] (self):
27
        template.html_top(_('Welcome to Larpe reverse proxy'))
28
        '<ul><li><a href="%s/admin/">%s</a></li></ul>' % (get_request().environ['SCRIPT_NAME'],
29
                                                         _('Configure Larpe'))
30

  
31
    def _q_traverse(self, path):
32
        response = get_response()
33
        response.filter = {}
34

  
35
        return Directory._q_traverse(self, path)
36

  
37
    def _q_lookup(self, component):
38
        return redirect(component + '/')
39

  
40
    def token [html] (self):
41
        session = get_session()
42

  
43
        if not hasattr(session, str('name_identifier')) or not session.name_identifier \
44
                or not hasattr(session, str('lasso_dump')) or not session.lasso_dump:
45
            raise errors.AccessUnauthorizedError()
46

  
47
        # If the token is in the query string, use it
48
        query_string = get_request().get_query()
49
        if query_string:
50
            parameters = query_string.split(str('&'))
51
            for param in parameters:
52
                values = param.split(str('='))
53
                if len(values) < 2:
54
                    continue
55
                if values[0] == str('token'):
56
                    return self._federate_token(values[1])
57

  
58
        # Otherwise, display a form to ask for the token
59
        form = Form(enctype='multipart/form-data')
60
        form.add(StringWidget, 'token', title = _('Identification Token'),
61
                required = True, size = 30)
62
        form.add_submit('submit', _('Submit'))
63
        form.add_submit('cancel', _('Cancel'))
64

  
65
        if form.get_widget('cancel').parse():
66
            return redirect('.')
67

  
68
        if not form.is_submitted() or form.has_errors():
69
            template.html_top(_('Identification Token'))
70
            '<p>'
71
            _('Please enter your identification token. ')
72
            _('Your local account will be federated with your Liberty Alliance account.')
73
            '</p>'
74
            form.render()
75
        else:
76
            session = get_session()
77

  
78
            if not hasattr(session, str('name_identifier')) or not session.name_identifier \
79
                    or not hasattr(session, str('lasso_dump')) or not session.lasso_dump:
80
                raise errors.AccessUnauthorizedError()
81

  
82
            token = form.get_widget('token').parse()
83

  
84
            return self._federate_token(token)
85

  
86
    def _federate_token(self, token):
87
        session = get_session()
88

  
89
        # Get the user who owns this token    
90
        users_with_token = list(User.select(lambda x: x.identification_token == token))
91
        if len(users_with_token) == 0:
92
            return template.error_page(_('Unknown Token'))
93

  
94
        # Fill user attributes
95
        user = users_with_token[0]
96
        user.name_identifiers.append(session.name_identifier)
97
        user.lasso_dumps = [ session.lasso_dump ]
98
        user.identification_token = None
99
        user.is_admin = True
100
        user.store()
101

  
102
        # Set this user in the session
103
        session.set_user(user.id, session.provider_id)
104

  
105
        # Delete now useless session attributes
106
        del session.name_identifier
107
        del session.lasso_dump
108
        del session.provider_id
109

  
110
        return redirect('%s/admin/' % get_request().environ['SCRIPT_NAME'])
111

  
larpe/branches/idwsf/larpe/saml2.ptl
1
import os
2
import sys
3
import urlparse
4

  
5
try:
6
    import lasso
7
except ImportError:
8
    print >> sys.stderr, 'Missing Lasso module, SAMLv2 support disabled'
9

  
10
from quixote import get_publisher, get_request, get_response, get_session, get_session_manager, redirect
11

  
12
from qommon.liberty import SOAPException, soap_call
13
from qommon.saml2 import Saml2Directory
14
from qommon import template
15
from qommon import get_logger
16

  
17
import misc
18
from users import User
19
from hosts import Host
20
from federations import Federation
21
import site_authentication
22

  
23
class Saml2(Saml2Directory):
24
    _q_exports = Saml2Directory._q_exports + ['local_auth']
25

  
26
    def login(self):
27
        return self.perform_login()
28

  
29
    def perform_login(self, idp = None):
30
        server = misc.get_lasso_server(protocol = 'saml2')
31
        if not server:
32
            return template.error_page(_('SAML 2.0 support not yet configured.'))
33
        login = lasso.Login(server)
34
        login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
35
        login.request.nameIDPolicy.format = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
36
        login.request.nameIDPolicy.allowCreate = True
37
        login.request.forceAuthn = False
38
        login.request.isPassive = False
39
        login.request.consent = 'urn:oasis:names:tc:SAML:2.0:consent:current-implicit'
40
        login.buildAuthnRequestMsg()
41
        return redirect(login.msgUrl)
42

  
43
    def singleSignOnArtifact(self):
44
        server = misc.get_lasso_server(protocol = 'saml2')
45
        if not server:
46
            return template.error_page(_('SAML 2.0 support not yet configured.'))
47
        login = lasso.Login(server)
48
        request = get_request()
49
        try:
50
            login.initRequest(request.get_query(), lasso.HTTP_METHOD_ARTIFACT_GET)
51
        except lasso.Error, error:
52
            if error[0] == lasso.PROFILE_ERROR_MISSING_ARTIFACT:
53
                return template.error_page(_('Missing SAML Artifact'))
54
            else:
55
                raise
56

  
57
        login.buildRequestMsg()
58
        #remote_provider_cfg = get_cfg('idp', {}).get(misc.get_provider_key(login.remoteProviderId))
59
        #client_cert = remote_provider_cfg.get('clientcertificate')
60

  
61
        try:
62
            soap_answer = soap_call(login.msgUrl, login.msgBody)
63
        except SOAPException:
64
            return template.error_page(_('Failure to communicate with identity provider'))
65

  
66
        try:
67
            login.processResponseMsg(soap_answer)
68
        except lasso.Error, error:
69
            if error[0] == lasso.LOGIN_ERROR_STATUS_NOT_SUCCESS:
70
                return template.error_page(_('Unknown authentication failure'))
71
            if error[0] == lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
72
                return template.error_page(_('Authentication failure; unknown principal'))
73
            if error[0] == lasso.LOGIN_ERROR_FEDERATION_NOT_FOUND:
74
                return template.error_page('there was no federation')
75
            raise
76

  
77
        return self.sso_after_response(login)
78

  
79
    def sso_after_response(self, login):
80
        try:
81
            assertion = login.response.assertion[0]
82
            if assertion.subject.subjectConfirmation.subjectConfirmationData.recipient != \
83
                        get_request().get_url():
84
                return template.error_page('SubjectConfirmation Recipient Mismatch')
85
        except:
86
                return template.error_page('SubjectConfirmation Recipient Mismatch')
87

  
88
        assertions_dir = os.path.join(get_publisher().app_dir, 'assertions')
89
        if not os.path.exists(assertions_dir):
90
            os.mkdir(assertions_dir)
91

  
92
        assertion_fn = os.path.join(assertions_dir, assertion.iD)
93
        if os.path.exists(assertion_fn):
94
            return template.error_page('Assertion replay')
95

  
96
        try:
97
            if assertion.subject.subjectConfirmation.method != \
98
                        'urn:oasis:names:tc:SAML:2.0:cm:bearer':
99
                return template.error_page('Unknown SubjectConfirmation Method')
100
        except:
101
            return template.error_page('Unknown SubjectConfirmation Method')
102

  
103
        try:
104
            audience_ok = False
105
            for audience_restriction in assertion.conditions.audienceRestriction:
106
                if audience_restriction.audience != login.server.providerId:
107
                    return template.error_page('Incorrect AudienceRestriction')
108
                audience_ok = True
109
            if not audience_ok:
110
                return template.error_page('Incorrect AudienceRestriction')
111
        except:
112
            return template.error_page('Incorrect AudienceRestriction')
113

  
114
#        try:
115
#            current_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
116
#            not_before = assertion.subject.subjectConfirmation.subjectConfirmationData.notBefore
117
#            not_on_or_after = assertion.subject.subjectConfirmation.subjectConfirmationData.notOnOrAfter
118
#            if not_before and current_time < not_before:
119
#                return template.error_page('Assertion received too early')
120
#            if not_on_or_after and current_time > not_on_or_after:
121
#                return template.error_page('Assertion expired')
122
#        except:
123
#            return template.error_page('Error checking Assertion Time')
124

  
125
        # TODO: check for unknown conditions
126

  
127
        login.acceptSso()
128

  
129
        session = get_session()
130
        if login.isSessionDirty:
131
            if login.session:
132
                session.lasso_session_dumps[login.server.providerId] = login.session.dump()
133
            else:
134
                session.lasso_session_dumps[login.server.providerId] = None
135

  
136
        if assertion.authnStatement[0].sessionIndex:
137
            session.lasso_session_index = assertion.authnStatement[0].sessionIndex
138

  
139
        user = self.lookup_user(session, login)
140

  
141
        # Check if it is for Larpe administration or token
142
        host = Host.get_host_from_url()
143
        if host is None:
144
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
145
        if host.name == 'larpe':
146
            session.name_identifier = login.nameIdentifier.content
147
            session.lasso_dump = login.identity.dump()
148
            session.provider_id = login.server.providerId
149

  
150
            if user:
151
                session.set_user(user.id, login.server.providerId)
152
                if hasattr(session, 'after_url') and session.after_url is not None and \
153
                        session.after_url.find('admin') != -1:
154
                    return redirect(session.after_url)
155
            else:
156
                if not user or not user.is_admin:
157
                    if hasattr(session, 'after_url') and session.after_url is not None \
158
                            and session.after_url.find('token') != -1:
159
                        return redirect(session.after_url)
160
                    return redirect('%s/token' % get_request().environ['SCRIPT_NAME'])
161
            return redirect('%s/admin/' % get_request().environ['SCRIPT_NAME'])
162

  
163
        # Set session user
164
        if not user:
165
            user = User()
166
        user.name_identifiers = [ login.nameIdentifier.content ]
167
        user.lasso_dumps = [ login.identity.dump() ]
168
        user.store()
169
        session.set_user(user.id, login.server.providerId)
170

  
171
        # Check if a federation already exist
172
        federations = Federation.select(lambda x: host.id == x.host_id \
173
                                        and user.name_identifiers[0] in x.name_identifiers)
174

  
175
        if federations:
176
            return site_authentication.get_site_authentication(host).sso_local_login(federations[0])
177
        else:
178
            # Build response redirection
179
            response = get_response()
180
            if session.after_url:
181
                after_url = session.after_url
182
                session.after_url = None
183
                return redirect(after_url)
184
            response.set_status(303)
185
            response.headers['location'] = urlparse.urljoin(get_request().get_url(), str('local_auth'))
186
            response.content_type = 'text/plain'
187
            return 'Your browser should redirect you'
188

  
189
    def slo_sp(self, method = None):
190
        host = Host.get_host_from_url()
191
        if host is None:
192
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
193

  
194
        if method is None:
195
            method = lasso.HTTP_METHOD_REDIRECT
196

  
197
        logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
198
        session = get_session()
199

  
200
        if not session.id or not session.users.has_key(logout.server.providerId) \
201
                or not session.lasso_session_dumps.has_key(logout.server.providerId):
202
            get_session_manager().expire_session(logout.server.providerId)
203
            return redirect(host.get_root_url())
204
        logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
205
        user = session.get_user(logout.server.providerId)
206
        site_authentication.get_site_authentication(host).local_logout(user=user)
207
        if user and user.lasso_dumps:
208
            logout.setIdentityFromDump(user.lasso_dumps[0])
209

  
210
        if method == lasso.HTTP_METHOD_REDIRECT:
211
            return self.slo_sp_redirect(logout)
212

  
213
        # Not implemented yet
214
        if method == lasso.HTTP_METHOD_SOAP:
215
            return self.slo_sp_soap(logout)
216

  
217
    def slo_sp_redirect(self, logout):
218
        session = get_session()
219
        try:
220
            logout.initRequest(None, lasso.HTTP_METHOD_REDIRECT)
221
        except lasso.Error, error:
222
            if error[0] == lasso.PROFILE_ERROR_NAME_IDENTIFIER_NOT_FOUND:
223
                get_session_manager().expire_session(logout.server.providerId) 
224
                return redirect(host.get_root_url())
225
            if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
226
                get_session_manager().expire_session(logout.server.providerId) 
227
                return redirect(host.get_root_url())
228
            raise
229

  
230
        logout.buildRequestMsg()
231
        return redirect(logout.msgUrl)
232

  
233
    def singleLogoutReturn(self):
234
        host = Host.get_host_from_url()
235
        if host is None:
236
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
237

  
238
        logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
239
        session = get_session()
240

  
241
        if not session.id or not session.users.has_key(logout.server.providerId) \
242
                or not session.lasso_session_dumps.has_key(logout.server.providerId):
243
            get_session_manager().expire_session(logout.server.providerId)
244
            return redirect(host.get_root_url())
245
        logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
246

  
247
        message = get_request().get_query()
248
        return self.slo_return(logout, message)
249

  
250
    def slo_return(self, logout, message):
251
        host = Host.get_host_from_url()
252

  
253
        session = get_session()
254

  
255
        try:
256
            logout.processResponseMsg(message)
257
        except lasso.Error, error:
258
            if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
259
                get_logger().warn('Invalid response')
260
            elif error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
261
                get_logger().warn('Failed to check single logout request signature')
262
            elif error[0] == lasso.LOGOUT_ERROR_REQUEST_DENIED:
263
                get_logger().warn('Request Denied')
264
            elif error[0] == lasso.LOGOUT_ERROR_UNKNOWN_PRINCIPAL:
265
                get_logger().warn('Unknown principal on logout, probably session stopped already on IdP')
266
                # XXX: wouldn't work when logged on two IdP
267
                del session.lasso_session_dumps[logout.server.providerId]
268
            else:
269
                raise
270

  
271
        if not session.lasso_session_dumps.has_key(logout.server.providerId):
272
            get_session_manager().expire_session(logout.server.providerId)
273

  
274
        return redirect(host.get_root_url())
275

  
276
    def singleLogoutSOAP(self):
277
        try:
278
            soap_message = self.get_soap_message()
279
        except:
280
            return
281

  
282
        response = get_response()
283
        response.set_content_type('text/xml')
284

  
285
        request_type = lasso.getRequestTypeFromSoapMsg(soap_message) 
286

  
287
        if request_type != lasso.REQUEST_TYPE_LOGOUT:
288
            get_logger().warn('SOAP message on single logout url not a slo message')
289
            return
290

  
291
        logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
292
        logout.processRequestMsg(soap_message)
293
        name_identifier = logout.nameIdentifier.content
294
        for session in get_session_manager().values():
295
            user = session.get_user(logout.server.providerId)
296
            if user and logout.nameIdentifier.content in user.name_identifiers:
297
                get_logger().info('SLO/SOAP from %s' % logout.remoteProviderId)
298
                break
299
        else:
300
            # no session, build straight failure answer
301
            logout.buildResponseMsg()
302
            return logout.msgBody
303

  
304
        return self.slo_idp(logout, session)
305

  
306
    def singleLogout(self):
307
        logout = lasso.Logout(misc.get_lasso_server(protocol = 'saml2'))
308
        try:
309
            logout.processRequestMsg(get_request().get_query())
310
        except lasso.Error, error:
311
            if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
312
                return template.error_page(_('Failed to check single logout request signature.'))
313
            raise
314
        session = get_session()
315
        if not session.id:
316
            # session has not been found, this may be because the user has
317
            # its browser configured so that cookies are not sent for
318
            # remote queries and IdP is using image-based SLO.
319
            # so we look up a session with the appropriate name identifier
320
            name_identifier = logout.nameIdentifier.content
321
            for session in get_session_manager().values():
322
                # This block differs from qommon
323
                user = session.get_user(logout.server.providerId)
324
                if user and logout.nameIdentifier.content in user.name_identifiers:
325
                    break
326
            else:
327
                session = get_session()
328

  
329
        return self.slo_idp(logout, session)
330

  
331
    def slo_idp(self, logout, session):
332
        # This block differs from qommon
333
        if session.lasso_session_dumps.has_key(logout.server.providerId):
334
            logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
335
        user = session.get_user(logout.server.providerId)
336
        if user and user.lasso_dumps:
337
            logout.setIdentityFromDump(user.lasso_dumps[0])
338

  
339
        if user and logout.nameIdentifier.content not in user.name_identifiers:
340
            raise 'no appropriate name identifier in session (%s and %s)' % (
341
                    logout.nameIdentifier.content, session.name_identifier)
342

  
343
        try:
344
            assertion = logout.session.getAssertions(logout.remoteProviderId)[0]
345
            if logout.request.sessionIndex and (
346
                    assertion.authnStatement[0].sessionIndex != logout.request.sessionIndex):
347
                logout.setSessionFromDump('<Session />')
348
        except:
349
            pass
350

  
351
        try:
352
            logout.validateRequest()
353
        except lasso.Error, error:
354
            if error[0] == lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
355
                pass
356
            elif error[0] == lasso.PROFILE_ERROR_IDENTITY_NOT_FOUND:
357
                pass
358
            elif error[0] == lasso.PROFILE_ERROR_MISSING_ASSERTION:
359
                pass
360
            else:
361
                raise
362
        else:
363
            get_session_manager().expire_session(logout.server.providerId)
364

  
365
        try:
366
            if not logout.request.sessionIndex:
367
                for session2 in get_session_manager().values():
368
                    if name_identifier == session2.name_identifier:
369
                        del get_session_manager()[session2.id]
370
        except:
371
            # killing all session failed, ignoring silently
372
            pass
373

  
374
        logout.buildResponseMsg()
375
        if logout.msgBody: # soap answer
376
            return logout.msgBody
377
        else:
378
            return redirect(logout.msgUrl)
379

  
380
    def lookup_user(self, session, login): 
381
        found_users = list(User.select(lambda x: login.nameIdentifier.content in x.name_identifiers))
382
        if found_users:
383
            return found_users[0]
384
        return None
385

  
386
    def local_auth(self):
387
        host = Host.get_host_from_url()
388
        if host is None:
389
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
390
        return site_authentication.get_site_authentication(host).local_auth
391
    local_auth = property(local_auth)
392

  
393
    def metadata(self):
394
        host = Host.get_host_from_url()
395
        if host is None:
396
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
397
        get_response().set_content_type('text/xml', 'utf-8')
398
        metadata = unicode(open(host.saml2_metadata).read(), 'utf-8')
399
        return metadata
400

  
401
    def public_key(self):
402
        host = Host.get_host_from_url()
403
        if host is None:
404
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
405
        get_response().set_content_type('text/plain')
406
        public_key = open(host.public_key).read()
407
        return public_key
408

  
larpe/branches/idwsf/larpe/sessions.py
1
import random
2

  
3
from quixote.session import Session, SessionManager
4
from quixote import get_request
5
from quixote.html import htmltext
6

  
7
from storage import StorableObject
8
from users import User
9

  
10
class BasicSession(Session, StorableObject):
11
    _names = 'sessions'
12

  
13
    users = {}
14
#    name_identifiers = {}
15
#    name_identifier = None
16
    after_url = None
17
    anonymous_key = None
18
    lasso_session_dumps = {}
19
#    lasso_session_dump = None
20
#    lasso_anonymous_identity_dump = None
21
    tempfiles = None
22
    magictokens = None
23
    message = None
24

  
25
    def has_info(self):
26
        return self.users or self.after_url or self.anonymous_key or \
27
            self.tempfiles or self.lasso_session_dumps or self.message or \
28
            self.magictokens or Session.has_info(self)
29
    is_dirty = has_info
30

  
31
    def get_session_id(self):
32
        return self.id
33

  
34
    def set_session_id(self, session_id):
35
        self.id = session_id
36

  
37
    session_id = property(get_session_id, set_session_id)
38

  
39
    def get_anonymous_key(self, generate = False):
40
        if self.anonymous_key:
41
            return self.anonymous_key
42
        if generate:
43
            self.anonymous_key = random.randint(0, 1000000000)
44
        return self.anonymous_key
45

  
46
    def display_message(self):
47
        if not self.message:
48
            return ''
49
        s = htmltext('<div class="%snotice">%s</div>' % self.message)
50
        self.message = None
51
        return s
52

  
53
    def add_magictoken(self, token, data):
54
        if not self.magictokens:
55
            self.magictokens = {}
56
        self.magictokens[token] = data
57

  
58
    def get_by_magictoken(self, token, default = None):
59
        if not self.magictokens:
60
            return default
61
        return self.magictokens.get(token, default)
62

  
63
    def get_user(self, provider_id):
64
        user_id = self.users.get(provider_id, None)
65
        if user_id:
66
            try:
67
                user = User.get(user_id)
68
            except KeyError:
69
                user = User()
70
#            if str(user_id).startswith('anonymous-'):
71
                user.id = user_id
72
#                user.anonymous = True
73
#                if self.name_identifiers.has_key(providerId):
74
#                    if not user.name_identifiers.has_key(providerId):
75
#                        user.name_identifiers[providerId] = [ self.name_identifiers[providerId] ]
76

  
77
#                if self.name_identifiers.has_key(providerId):
78
#                    user.name_identifiers[providerId] = [ self.name_identifiers[providerId] ]
79
#                else:
80
#                    user.name_identifiers[providerId] = []
81
#                user.lasso_dumps[providerId] = self.lasso_anonymous_identity_dump
82
            return user
83
        return None
84
        
85
    def set_user(self, user_id, provider_id):
86
        self.users[provider_id] = user_id
87

  
88

  
89
class StorageSessionManager(SessionManager):
90
    def __init__(self):
91
        SessionManager.__init__(self, session_class=BasicSession)
92

  
93
    def forget_changes(self, session):
94
        pass
95

  
96
    def __getitem__(self, session_id):
97
        try:
98
            return BasicSession.get(session_id)
99
        except KeyError:
100
            raise KeyError
101

  
102
    def get(self, session_id, default = None):
103
        try:
104
            return BasicSession.get(session_id)
105
        except KeyError:
106
            return default
107

  
108
    def commit_changes(self, session):
109
        if session and session.id:
110
            session.store()
111
        
112
    def keys(self):
113
        return BasicSession.keys()
114

  
115
    def values(self):
116
        return BasicSession.values()
117

  
118
    def items(self):
119
        return BasicSession.items()
120

  
121
    def has_key(self, session_id):
122
        return BasicSession.has_key(session_id)
123

  
124
    def __setitem__(self, session_id, session):
125
        session.store()
126

  
127
    def __delitem__(self, session_id):
128
        if not session_id:
129
            return
130
        try:
131
            BasicSession.remove_object(session_id)
132
        except OSError:
133
            raise KeyError
134

  
135
    def expire_session(self, provider_id=None):
136
        session = get_request().session
137
        if session.id is not None:
138
            if provider_id:
139
                if session.users.has_key(provider_id):
140
                    del session.users[provider_id]
141
                if session.lasso_session_dumps.has_key(provider_id):
142
                    del session.lasso_session_dumps[provider_id]
143
                session.store()
144
            if not session.users:
145
                SessionManager.expire_session(self)
146
#                session.remove_self()
larpe/branches/idwsf/larpe/site_authentication.ptl
1
import libxml2
2
import urllib
3
import urlparse
4
import httplib
5
import re
6
import os
7
import socket
8
import base64
9

  
10
from quixote import get_request, get_response, get_session, redirect, get_publisher
11
from quixote.directory import Directory
12
from quixote.http_request import parse_header
13

  
14
import lasso
15

  
16
from qommon import get_logger
17
from qommon.form import *
18
from qommon.errors import ConnectionError, ConfigurationError, LoginError
19
from qommon.misc import http_post_request, http_get_page
20
from qommon.template import *
21

  
22
import misc
23
import storage
24
from users import User
25
from federations import Federation
26

  
27
class SiteAuthentication:
28
    def __init__(self, host):
29
        self.host = host
30

  
31
    def federate(self, username, password, provider_id, cookies, select):
32
        user = get_session().get_user(provider_id)
33
        if user is not None:
34
            Federation(username, password, self.host.id, user.name_identifiers[0], cookies, select).store()
35

  
36
#     def federate_token(self, token):
37
#         token_dir = os.path.join(misc.get_proxied_site_path(), 'tokens')
38
#         token_file_name = os.path.join(token_dir, token)
39
#         token_file = open(token_file_name, 'r')
40
#         username, user_passwd = token_file.read().split()        
41
#         token_file.close()
42

  
43
    def sso_local_login(self, federation):
44
        status, data = self.local_auth_check_dispatch(
45
            federation.username, federation.password, federation.select_fields)
46
        success, return_content = self.check_auth(status, data)
47
        if success:
48
            session = get_session()
49
            if hasattr(session, 'cookies'):
50
                federation.set_cookies(session.cookies)
51
                federation.store()
52
            return return_content
53
        else:
54
            return redirect('local_auth')
55

  
56
    def local_auth [html] (self, first_time=True):
57
        response = get_response()
58
        response.set_content_type('text/html')
59

  
60
        if hasattr(get_response(), str('breadcrumb')):
61
            del get_response().breadcrumb
62

  
63
        get_response().filter['default_org'] = '%s - %s' % (self.host.label, _('Local authentication')) 
64
        get_response().filter['body_class'] = 'login'
65

  
66
        form = self.form_local_auth()
67
        form.add_submit('submit', _('Submit'))
68
        #form.add_submit('cancel', _('Cancel'))
69

  
70
#        if form.get_widget('cancel').parse():
71
#            return redirect('.')
72
        authentication_failure = None
73
        if form.is_submitted() and not form.has_errors():
74
            try:
75
                return self.submit_local_auth_form(form)
76
            except LoginError:
77
                authentication_failure = _('Authentication failure')
78
                get_logger().info('local auth page : %s' % authentication_failure)
79
            except ConnectionError, err:
80
                authentication_failure = _('Connection failed : %s') % err
81
                get_logger().info('local auth page : %s' % authentication_failure)
82
            except ConfigurationError, err:
83
                authentication_failure = _('This service provider is not fully configured : %s') % err
84
                get_logger().info('local auth page : %s' % authentication_failure)
85
            except Exception, err:
86
                authentication_failure = _('Unknown error : %s' % err)
87
                get_logger().info('local auth page : %s' % authentication_failure)
88

  
89
        if authentication_failure:
90
            '<div class="errornotice">%s</div>' % authentication_failure
91
        '<p>'
92
        _('Please type your login and password for this Service Provider.')
93
        _('Your local account will be federated with your Liberty Alliance account.')
94
        '</p>'
95

  
96
        form.render()
97

  
98
    # Also used in admin/hosts.ptl
99
    def form_local_auth(self):
100
        form = Form(enctype='multipart/form-data')
101
        form.add(StringWidget, 'username', title = _('Username'), required = True,
102
                size = 30)
103
        form.add(PasswordWidget, 'password', title = _('Password'), required = True,
104
                size = 30)
105
        for name, values in self.host.select_fields.iteritems():
106
            options = []
107
            if values:
108
                for value in values:
109
                    options.append(value)
110
                form.add(SingleSelectWidget, name, title = name.capitalize(),
111
                    value = values[0], options = options)
112
        return form
113

  
114
    def submit_local_auth_form(self, form):
115
        username = form.get_widget('username').parse()
116
        password = form.get_widget('password').parse()
117
        select = {}
118
        for name, values in self.host.select_fields.iteritems():
119
            if form.get_widget(name):
120
                select[name] = form.get_widget(name).parse()
121
        return self.local_auth_check(username, password, select)
122

  
123
    def local_auth_check(self, username, password, select={}):
124
        status, data = self.local_auth_check_dispatch(username, password, select)
125
        if status == 0:
126
            raise
127
        success, return_content = self.check_auth(status, data)
128
        if success:
129
            if misc.get_current_protocol() == lasso.PROTOCOL_SAML_2_0:
130
                provider_id = self.host.saml2_provider_id
131
            else:
132
                provider_id = self.host.provider_id
133
            session = get_session()
134

  
135
            if hasattr(session, 'cookies'):
136
                self.federate(username, password, provider_id, session.cookies, select)
137
            else:
138
                self.federate(username, password, provider_id, None, select)
139
            return return_content
140
        raise LoginError()
141

  
142
    def local_auth_check_dispatch(self, username, password, select={}):
143
        if self.host.auth_mode == 'http_basic':
144
            return self.local_auth_check_http_basic(username, password)
145
        elif self.host.auth_mode == 'form' and hasattr(self.host, 'auth_check_url') \
146
                and self.host.auth_check_url is not None:
147
            return self.local_auth_check_post(username, password, select)
148
        else:
149
            raise ConfigurationError('No authentication form was found')
150

  
151
    def local_auth_check_post(self, username, password, select={}):
152
        url = self.host.auth_check_url
153

  
154
        # Build request body
155
        if self.host.post_parameters:
156
            body_params = {}
157
            # Login field
158
            if self.host.post_parameters[self.host.login_field_name]['enabled'] is True:
159
                body_params[self.host.login_field_name] = username
160
            # Password field
161
            if self.host.post_parameters[self.host.password_field_name]['enabled'] is True:
162
                body_params[self.host.password_field_name] = password
163
            # Select fields
164
            for name, value in select.iteritems():
165
                if self.host.post_parameters[name]['enabled'] is True:
166
                    body_params[name] = value
167
            # Other fields (hidden, submit and custom)
168
            for name, value in self.host.other_fields.iteritems():
169
                if self.host.post_parameters[name]['enabled'] is True:
170
                    body_params[name] = self.host.post_parameters[name]['value']
171
            body = urllib.urlencode(body_params)
172
        else:
173
            # XXX: (to be removed later) Send all parameters for sites configured with a previous version of Larpe
174
            body = '%s=%s&%s=%s' % (self.host.login_field_name, username, self.host.password_field_name, password)
175
            # Add select fields to the body
176
            for name, value in select.iteritems():
177
                body += '&%s=%s' % (name, value)
178
            # Add hidden fields to the body
179
            if self.host.send_hidden_fields:
180
                for name, value in self.host.other_fields.iteritems():
181
                    body += '&%s=%s' % (name, value)
182

  
183
        # Build request HTTP headers
184
        if self.host.http_headers:
185
            headers = {}
186
            if self.host.http_headers['X-Forwarded-For']['enabled'] is True:
187
                headers['X-Forwarded-For'] = get_request().get_environ('REMOTE_ADDR', '-')
188
            for name, value in self.host.http_headers.iteritems():
189
                if value['enabled'] is True and value['immutable'] is False:
190
                    headers[name] = value['value']
191
        else:
192
            # XXX: (to be removed later) Send default headers for sites configured with a previous version of Larpe
193
            headers = { 'Content-Type': 'application/x-www-form-urlencoded',
194
                        'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
195
                        'X-Forwarded-Host': self.host.reversed_hostname }
196

  
197
        # Send request
198
        response, status, data, auth_headers = http_post_request(url, body, headers, self.host.use_proxy)
199

  
200
        cookies = response.getheader('Set-Cookie', None)
201
        self.host.cookies = []
202
        if cookies is not None:
203
            cookies_list = []
204
            cookies_set_list = []
205
            for cookie in cookies.split(', '):
206
                # Drop the path and other attributes
207
                cookie_only = cookie.split('; ')[0]
208
                regexp = re.compile('=')
209
                if regexp.search(cookie_only) is None:
210
                    continue
211
                # Split name and value
212
                cookie_split = cookie_only.split('=')
213
                cookie_name = cookie_split[0]
214
                cookie_value = cookie_split[1]
215
                cookies_list.append('%s=%s' % (cookie_name, cookie_value))
216
                set_cookie = '%s=%s; path=/' % (cookie_name, cookie_value)
217
                cookies_set_list.append(set_cookie)
218
                self.host.cookies.append(cookie_name)
219
            cookies_headers = '\r\nSet-Cookie: '.join(cookies_set_list)
220
            get_response().set_header('Set-Cookie', cookies_headers)
221
            self.host.store()
222
            get_session().cookies = '; '.join(cookies_list)
223
        else:
224
            get_logger().warn('No cookie from local authentication')
225

  
226
        return response.status, data
227

  
228
    def local_auth_check_http_basic(self, username, password):
229
        url = self.host.auth_form_url
230
        hostname, query = urllib.splithost(url[5:])
231
        conn = httplib.HTTPConnection(hostname)
232

  
233
        auth_header = 'Basic %s' % base64.encodestring('%s:%s' % (username, password))
234

  
235
        try:
236
            conn.request('GET', query, headers={'Authorization': auth_header})
237
        except socket.gaierror, err:
238
            print err
239
            conn.close()
240
            return 0, None
241
        else:
242
            response = conn.getresponse()
243
            conn.close()
244
            return response.status, response.read()
245

  
246
    def check_auth(self, status, data):
247
        success = False
248
        return_content = ''
249

  
250
        # If status is 500, fail without checking other criterias
251
        if status // 100 == 5:
252
            success = False
253
            return_content = redirect(self.host.get_return_url())
254

  
255

  
256
        # For http auth, only check status code
257
        elif self.host.auth_mode == 'http_basic':
258
            # If failed, status code should be 401
259
            if status // 100 == 2 or status // 100 == 3:
260
                success = True
261
                return_content = redirect(self.host.get_return_url())
262

  
263
        else:
264
            if self.host.auth_system == 'password':
265
                # If there is a password field, authentication probably failed
266
                regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
267
                if not regexp.findall(data):
268
                    success = True
269
                    return_content = redirect(self.host.get_return_url())
270
            elif self.host.auth_system == 'status':
271
                match_status = int(self.host.auth_match_status)
272
                if match_status == status:
273
                    success = True
274
                    return_content = redirect(self.host.get_return_url())
275
            elif self.host.auth_system == 'match_text':
276
                # If the auth_match_text is not matched, it means the authentication is successful
277
                regexp = re.compile(self.host.auth_match_text, re.DOTALL)
278
                if not regexp.findall(data):
279
                    success = True
280
                    return_content = redirect(self.host.get_return_url())
281

  
282
#        if status == 302:
283
#            success = True
284
#            return_content = redirect(self.host.return_url)
285
#        else:
286
#            if self.host.orig_site.startswith('http://listes.entrouvert.com') \
287
#                    and re.search('You have logged in', data) is not None:
288
#                success = True
289
#                return_content = data
290
        return success, return_content
291

  
292

  
293
    def local_logout(self, federation=None, user=None):
294
        if federation is None and user is not None:
295
            federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
296
            if federations:
297
                federation = federations[0]
298

  
299
        # Logout request to the site
300
        url = self.host.logout_url            
301
        if url is not None and federation is not None and federation.cookies is not None:
302
            try:
303
                http_get_page(url, {'Cookie': federation.cookies})
304
            except:
305
                pass
306

  
307
        # Remove cookies from the browser
308
        if hasattr(self.host, 'cookies'):
309
            for cookie in self.host.cookies:
310
                get_response().expire_cookie(cookie, path='/')
311

  
312
    def local_defederate(self, session, provider_id):
313
        if session is None:
314
            return
315
        user = session.get_user(provider_id)
316
        if user is not None:
317
            federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
318
            for federation in federations:
319
                self.local_logout(provider_id, federation)
320
                federation.remove_name_identifier(user.name_identifiers[0])
321
                federation.store()
322

  
323
site_authentication_classes = {}
324

  
325
def register_site_authentication_class(klass):
326
    site_authentication_classes[klass.plugin_name] = klass
327

  
328
def guess_site_authentication_class(html_doc):
329
    for name, klass in site_authentication_classes.iteritems():
330
        if klass.auto_detect_site(html_doc):
331
            return klass.plugin_name
332
    return None
333

  
334
def get_site_authentication(host):
335
    if host.site_authentication_plugin is None:
336
        return SiteAuthentication(host)
337
    return site_authentication_classes[host.site_authentication_plugin](host)
338

  
larpe/branches/idwsf/larpe/storage.py
1
import os
2
import cPickle
3

  
4
from quixote import get_publisher
5

  
6
def lax_int(s):
7
    try:
8
        return int(s)
9
    except ValueError:
10
        return -1
11

  
12
def fix_key(k):
13
    # insure key can be inserted in filesystem
14
    if not k: return k
15
    return str(k).replace('/', '-')
16

  
17

  
18
class StorableObject(object):
19
    def __init__(self):
20
        if get_publisher():
21
            self.id = self.get_new_id()
22

  
23
    def get_table_name(cls):
24
        return cls._names
25
    get_table_name = classmethod(get_table_name)
26

  
27
    def get_objects_dir(cls):
28
        return os.path.join(get_publisher().app_dir, cls.get_table_name())
29
    get_objects_dir = classmethod(get_objects_dir)
30

  
31
    def keys(cls):
32
        if not os.path.exists(cls.get_objects_dir()):
33
            return []
34
        return [fix_key(x) for x in os.listdir(cls.get_objects_dir())]
35
    keys = classmethod(keys)
36

  
37
    def values(cls):
38
        return [cls.get(x) for x in cls.keys()]
39
    values = classmethod(values)
40

  
41
    def items(cls):
42
        return [(x, cls.get(x)) for x in cls.keys()]
43
    items = classmethod(items)
44
    
45
    def count(cls):
46
        return len(cls.keys())
47
    count = classmethod(count)
48

  
49
    def select(cls, clause = None, order_by = None):
50
        objects = [cls.get(k) for k in cls.keys()]
51
        if clause:
52
            objects = [x for x in objects if clause(x)]
53
        if order_by:
54
            order_by = str(order_by)
55
            if order_by[0] == '-':
56
                reverse = True
57
                order_by = order_by[1:]
58
            else:
59
                reverse = False
60
            objects.sort(lambda x,y: cmp(getattr(x, order_by), getattr(y, order_by)))
61
            if reverse:
62
                objects.reverse()
63
        return objects
64
    select = classmethod(select)
65
    
66
    def has_key(cls, id):
67
        return fix_key(id) in cls.keys()
68
    has_key = classmethod(has_key)
69

  
70
    def get_new_id(cls):
71
        keys = cls.keys()
72
        if not keys:
73
            id = 1
74
        else:
75
            id = max([lax_int(x) for x in keys]) + 1
76
            if id == 0:
77
                id = len(keys)+1
78
        return id
79
    get_new_id = classmethod(get_new_id)
80

  
81
    def get(cls, id):
82
        if id is None:
83
            raise KeyError()
84
        try:
85
            s = open(os.path.join(cls.get_objects_dir(), fix_key(id))).read()
86
        except IOError:
87
            raise KeyError()
88
        try:
89
            o = cPickle.loads(s)
90
        except EOFError:
91
            raise KeyError()
92
        o._names = cls._names
93
        if hasattr(cls, 'migrate'):
94
            o.migrate()
95
        return o
96
    get = classmethod(get)
97

  
98
    def store(self):
99
        if not os.path.exists(self.get_objects_dir()):
100
            os.mkdir(self.get_objects_dir())
101
        s = cPickle.dumps(self)
102
        open(os.path.join(self.get_objects_dir(), fix_key(self.id)), 'w').write(s)
103

  
104
    def volatile(cls):
105
        o = cls()
106
        o.id = None
107
        return o
108
    volatile = classmethod(volatile)
109

  
110
    def remove_object(cls, id):
111
        os.unlink(os.path.join(cls.get_objects_dir(), fix_key(id)))
112
    remove_object = classmethod(remove_object)
113

  
114
    def remove_self(self):
115
        path = os.path.join(self.get_objects_dir(), fix_key(self.id))
116
        try:
117
            os.unlink(path)
118
        except OSError, err:
119
            print err
larpe/branches/idwsf/larpe/users.py
1
from storage import StorableObject
2

  
3
class User(StorableObject):
4
    _names = 'users'
5

  
6
    name = None
7
    email = None
8
    name_identifiers = None
9
    identification_token = None
10
#    lasso_dump = None
11
    lasso_dumps = None
12
    is_admin = False
13
    anonymous = False
14

  
15
    def __init__(self, name=None):
16
        StorableObject.__init__(self)
17
        self.name = name
18
        self.name_identifiers = []
19
        self.lasso_dumps = []
20

  
21
    def migrate(self):
22
        pass
23

  
24
    def remove_name_identifier(self, provider_id, name_identifier):
25
        self.name_identifiers.remove(name_identifier)
26
        if not self.name_identifiers:
27
            self.remove_self()
28
        else:
29
            self.store()
30

  
31
    def __str__(self):
32
        return 'User %s, name : %s, name identifiers : %s, lasso_dumps : %s, token : %s' \
33
                % (self.id, self.name, self.name_identifiers, self.lasso_dumps, self.identification_token)
larpe/branches/idwsf/larpectl
1
#!/usr/bin/python
2

  
3
import sys
4

  
5
from larpe import ctl
6

  
7
def print_usage():
8
    print 'Usage: larpectl command [...]'
9
    print ''
10
    print 'Commands:'
11
    print '  start                start server'
12
    print '  cache_modulesets     parse and cache jhbuild module sets'
13

  
14
if len(sys.argv) < 2:
15
    print_usage()
16
    sys.exit(1)
17
else:
18
    command = sys.argv[1]
19

  
20
    if command == 'start':
21
        ctl.start(sys.argv[2:])
22
    elif command == 'cache_modulesets':
23
        ctl.cache_modulesets()
24
    else:
25
        print_usage()
larpe/branches/idwsf/make_debian_package.sh
1
#!/bin/sh
2

  
3
VERSION="0.2.0"
4

  
5
svn export svn://labs.libre-entreprise.org/svnroot/larpe/larpe/tags/release-${VERSION}
6
cd release-${VERSION}
7
cvs -d :pserver:anonymous@labs.libre-entreprise.org:/cvsroot/wcs login
8
cvs -d :pserver:anonymous@labs.libre-entreprise.org:/cvsroot/wcs export -r HEAD -d larpe/qommon wcs/wcs/qommon
9
cd ..
10
mv release-${VERSION} larpe-${VERSION}
11
mv larpe-${VERSION}/debian .
12
tar czf larpe_${VERSION}.orig.tar.gz larpe-${VERSION}
13
mv debian larpe-${VERSION}
14
cd larpe-${VERSION}
15
debuild
16
cd ..
17
rm -rf larpe-${VERSION}
larpe/branches/idwsf/po/Makefile
1
prefix = /usr
2

  
3
POFILES=$(wildcard *.po)
4
MOFILES=$(POFILES:.po=.mo)
5
PYFILES=$(shell find -L ../larpe -name '*.py' -or -name '*.ptl')
6
RM=rm -f
7

  
8
all: $(MOFILES)
9

  
10
install: all
11
	for file in $(MOFILES); do \
12
		lang=`echo $$file | sed 's/\.mo//'`; \
13
		install -d $(DESTDIR)$(prefix)/share/locale/$$lang/LC_MESSAGES/; \
14
		install -m 0644 $$file $(DESTDIR)$(prefix)/share/locale/$$lang/LC_MESSAGES/larpe.mo; \
15
	done
16

  
17
uninstall:
18
	@for file in $(MOFILES); do \
19
		lang=`echo $$file | sed 's/\.mo//'`; \
20
		$(RM) $(DESTDIR)$(prefix)/share/locale/$$lang/LC_MESSAGES/larpe.mo; \
21
	done
22

  
23
clean:
24
	-$(RM) messages.mo $(MOFILES)
25

  
26
larpe.pot: $(PYFILES)
27
	@echo "Rebuilding the pot file"
28
	$(RM) larpe.pot tmp.*.pot
29
	cnt=0;
30
	for file in $(PYFILES); do \
31
		cnt=$$(expr $$cnt + 1); \
32
		bn=$$cnt.`basename $$file`; \
33
		xgettext --keyword=N_ -c -L Python -o tmp.$$bn.pot $$file; \
34
	done
35
	msgcat tmp.*.pot > larpe.pot
36
	$(RM) tmp.*.pot
37

  
38
%.mo: %.po
39
	msgfmt -o $@ $<
40

  
41
%.po: larpe.pot
42
	@echo -n "Merging larpe.pot and $@"
43
	@msgmerge $@ larpe.pot -o $@.new
44
	@if [ "`diff $@ $@.new | grep '[<>]' | wc -l`" -ne 2 ]; then \
45
		mv -f $@.new $@; \
46
	else \
47
		$(RM) $@.new; \
48
	fi
49
	@msgfmt --statistics $@
larpe/branches/idwsf/po/fr.po
1
msgid ""
2
msgstr ""
3
"Project-Id-Version: Larpe 0.2.0\n"
4
"Report-Msgid-Bugs-To: \n"
5
"POT-Creation-Date: 2007-12-18 08:49+0100\n"
6
"PO-Revision-Date: 2007-01-29 16:57+0200\n"
7
"Last-Translator: Damien Laniel <dlaniel@entrouvert.com>\n"
8
"Language-Team: French\n"
9
"MIME-Version: 1.0\n"
10
"Content-Type: text/plain; charset=utf-8\n"
11
"Content-Transfer-Encoding: 8bit\n"
12

  
13
#: ../larpe/liberty.ptl:45 ../larpe/qommon/liberty.ptl:43
14
msgid "Liberty support is not yet configured"
15
msgstr "Le support Liberty n'est pas encore configuré"
16

  
17
#: ../larpe/liberty.ptl:58 ../larpe/qommon/liberty.ptl:58
18
#: ../larpe/qommon/saml2.ptl:96 ../larpe/qommon/saml2.ptl:358
19
#: ../larpe/qommon/saml2.ptl:528 ../larpe/saml2.ptl:88 ../larpe/saml2.ptl:314
20
#: ../larpe/saml2.ptl:479
21
msgid "Failure to communicate with identity provider"
22
msgstr "Impossible de communiquer avec le fournisseur d'identités."
23

  
24
#: ../larpe/liberty.ptl:63 ../larpe/qommon/liberty.ptl:63
25
#: ../larpe/qommon/saml2.ptl:102 ../larpe/qommon/saml2.ptl:207
26
#: ../larpe/qommon/saml2.ptl:234 ../larpe/saml2.ptl:94 ../larpe/saml2.ptl:199
27
msgid "Unknown authentication failure"
28
msgstr "Erreur d'authentification inconnue"
29

  
30
#: ../larpe/liberty.ptl:66 ../larpe/qommon/liberty.ptl:66
31
#: ../larpe/qommon/saml2.ptl:104 ../larpe/saml2.ptl:96
32
msgid "Authentication failure; unknown principal"
33
msgstr "Erreur d'authentification: utilisateur inconnu"
34

  
35
#: ../larpe/liberty.ptl:67 ../larpe/qommon/liberty.ptl:69
36
msgid "Identity Provider didn't accept artifact transaction."
37
msgstr "Le fournisseur d'identité n'a pas accepté l'artifact."
38

  
39
#: ../larpe/liberty.ptl:163 ../larpe/liberty.ptl:193
40
#: ../larpe/qommon/liberty.ptl:119 ../larpe/qommon/liberty.ptl:145
41
#: ../larpe/qommon/saml2.ptl:442 ../larpe/saml2.ptl:277 ../larpe/saml2.ptl:395
42
msgid "Failed to check single logout request signature."
43
msgstr "Erreur à la vérification de la signature de la demande de déconnexion"
44

  
45
#: ../larpe/qommon/admin/texts.ptl:47 ../larpe/qommon/admin/texts.ptl:48
46
#: ../larpe/qommon/admin/texts.ptl:100 ../larpe/qommon/admin/texts.ptl:137
47
msgid "Texts"
48
msgstr "Textes"
49

  
50
#: ../larpe/qommon/admin/texts.ptl:57
51
msgid "Custom Text:"
52
msgstr "Texte personnalisé :"
53

  
54
#: ../larpe/qommon/admin/texts.ptl:57 ../larpe/qommon/admin/texts.ptl:133
55
#: ../larpe/qommon/admin/emails.ptl:90 ../larpe/qommon/admin/emails.ptl:172
56
msgid "description"
57
msgstr "description"
58

  
59
#: ../larpe/qommon/admin/texts.ptl:61 ../larpe/qommon/admin/emails.ptl:94
60
#: ../larpe/admin/settings.ptl:149 ../larpe/admin/hosts.ptl:592
61
#: ../larpe/admin/hosts.ptl:644 ../larpe/admin/hosts.ptl:1672
62
#: ../larpe/admin/hosts.ptl:1740 ../larpe/admin/users.ptl:186
63
msgid "Back"
64
msgstr "Retour"
65

  
66
#. #-#-#-#-#  tmp.23.idp.ptl.pot (PACKAGE VERSION)  #-#-#-#-#
67
#. TODO
68
#: ../larpe/qommon/admin/texts.ptl:82 ../larpe/qommon/admin/emails.ptl:64
69
#: ../larpe/qommon/admin/emails.ptl:116 ../larpe/qommon/ident/password.ptl:260
70
#: ../larpe/qommon/ident/password.ptl:510
71
#: ../larpe/qommon/ident/password.ptl:548
72
#: ../larpe/qommon/ident/password.ptl:741
73
#: ../larpe/qommon/ident/password.ptl:768 ../larpe/qommon/ident/idp.ptl:76
74
#: ../larpe/qommon/ident/idp.ptl:212 ../larpe/qommon/ident/idp.ptl:298
75
#: ../larpe/qommon/ident/idp.ptl:491 ../larpe/qommon/ident/idp.ptl:515
76
#: ../larpe/qommon/ident/idp.ptl:737 ../larpe/qommon/ident/idp.ptl:1022
77
#: ../larpe/admin/settings.ptl:30 ../larpe/admin/settings.ptl:116
78
#: ../larpe/admin/settings.ptl:165 ../larpe/admin/settings.ptl:310
79
#: ../larpe/admin/settings.ptl:449 ../larpe/admin/settings.ptl:466
80
#: ../larpe/admin/settings.ptl:495 ../larpe/admin/settings.ptl:536
81
#: ../larpe/admin/hosts.ptl:357 ../larpe/admin/hosts.ptl:624
82
#: ../larpe/admin/hosts.ptl:673 ../larpe/admin/hosts.ptl:721
83
#: ../larpe/admin/hosts.ptl:1092 ../larpe/admin/hosts.ptl:1393
84
#: ../larpe/admin/hosts.ptl:1440 ../larpe/admin/hosts.ptl:1464
85
#: ../larpe/admin/hosts.ptl:1685 ../larpe/admin/users.ptl:26
86
#: ../larpe/admin/users.ptl:38 ../larpe/admin/users.ptl:124
87
#: ../larpe/admin/logger.ptl:110 ../larpe/root.ptl:64
88
#: ../larpe/site_authentication.ptl:67 ../larpe/liberty_site.ptl:96
89
msgid "Submit"
90
msgstr "Valider"
91

  
92
#: ../larpe/qommon/admin/texts.ptl:84
93
msgid "Restore default text"
94
msgstr "Restaurer le texte par défaut"
95

  
96
#: ../larpe/qommon/admin/texts.ptl:85 ../larpe/qommon/admin/emails.ptl:65
97
#: ../larpe/qommon/admin/emails.ptl:119 ../larpe/qommon/ident/password.ptl:261
98
#: ../larpe/qommon/ident/password.ptl:511
99
#: ../larpe/qommon/ident/password.ptl:549
100
#: ../larpe/qommon/ident/password.ptl:743
101
#: ../larpe/qommon/ident/password.ptl:769 ../larpe/qommon/ident/idp.ptl:77
102
#: ../larpe/qommon/ident/idp.ptl:299 ../larpe/qommon/ident/idp.ptl:516
103
#: ../larpe/qommon/ident/idp.ptl:738 ../larpe/qommon/ident/idp.ptl:1023
104
#: ../larpe/admin/settings.ptl:117 ../larpe/admin/settings.ptl:167
105
#: ../larpe/admin/settings.ptl:311 ../larpe/admin/settings.ptl:450
106
#: ../larpe/admin/settings.ptl:467 ../larpe/admin/settings.ptl:496
107
#: ../larpe/admin/settings.ptl:537 ../larpe/admin/hosts.ptl:118
108
#: ../larpe/admin/hosts.ptl:358 ../larpe/admin/hosts.ptl:623
109
#: ../larpe/admin/hosts.ptl:674 ../larpe/admin/hosts.ptl:722
110
#: ../larpe/admin/hosts.ptl:1093 ../larpe/admin/hosts.ptl:1394
111
#: ../larpe/admin/hosts.ptl:1441 ../larpe/admin/hosts.ptl:1465
112
#: ../larpe/admin/hosts.ptl:1686 ../larpe/admin/users.ptl:27
113
#: ../larpe/admin/users.ptl:39 ../larpe/admin/users.ptl:125
114
#: ../larpe/admin/users.ptl:140 ../larpe/root.ptl:65
115
msgid "Cancel"
116
msgstr "Annuler"
117

  
118
#: ../larpe/qommon/admin/texts.ptl:97 ../larpe/qommon/admin/emails.ptl:131
119
#: ../larpe/admin/settings.ptl:179
120
msgid "Invalid template"
121
msgstr "Squelette invalide"
122

  
123
#: ../larpe/qommon/admin/texts.ptl:101
124
msgid "Text"
125
msgstr "Texte"
126

  
127
#: ../larpe/qommon/admin/menu.ptl:46
128
msgid "backoffice"
129
msgstr "backoffice"
130

  
131
#: ../larpe/qommon/admin/menu.ptl:47 ../larpe/qommon/backoffice/menu.ptl:38
132
#: ../larpe/admin/menu.ptl:45
133
msgid "logout"
134
msgstr "déconnexion"
135

  
136
#: ../larpe/qommon/admin/menu.ptl:67
137
#, python-format
138
msgid "Administration of %s"
139
msgstr "Administration de %s"
140

  
141
#: ../larpe/qommon/admin/menu.ptl:88 ../larpe/admin/menu.ptl:88
142
msgid "Add"
143
msgstr "Ajouter"
144

  
145
#: ../larpe/qommon/admin/menu.ptl:89 ../larpe/qommon/ident/idp.ptl:476
146
#: ../larpe/admin/menu.ptl:89 ../larpe/admin/hosts.ptl:1553
... Ce différentiel a été tronqué car il excède la taille maximale pouvant être affichée.

Formats disponibles : Unified diff