local idp (beta)
This commit is contained in:
parent
4af9283c39
commit
b62e4e4d33
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/*
|
||||
* privileges for univnautes IdP
|
||||
*
|
||||
*/
|
||||
|
||||
global $priv_list;
|
||||
|
||||
$priv_list['univnautes-idp'] = array();
|
||||
$priv_list['univnautes-idp']['name'] = "UnivNautes IdP - account";
|
||||
$priv_list['univnautes-idp']['descr'] = "Local IdP account";
|
||||
$priv_list['univnautes-idp']['match'] = array();
|
||||
$priv_list['univnautes-idp']['match'][] = "NOTHING";
|
||||
|
||||
$priv_list['univnautes-idp-multiple'] = array();
|
||||
$priv_list['univnautes-idp-multiple']['name'] = "UnivNautes IdP - multiple connections";
|
||||
$priv_list['univnautes-idp-multiple']['descr'] = "allowing multiple connections";
|
||||
$priv_list['univnautes-idp-multiple']['match'] = array();
|
||||
$priv_list['univnautes-idp-multiple']['match'][] = "NOTHING";
|
||||
|
||||
$priv_list['univnautes-idp-admin'] = array();
|
||||
$priv_list['univnautes-idp-admin']['name'] = "UnivNautes IdP - administrator";
|
||||
$priv_list['univnautes-idp-admin']['descr'] = "manage local IdP accounts";
|
||||
$priv_list['univnautes-idp-admin']['match'] = array();
|
||||
$priv_list['univnautes-idp-admin']['match'][] = "NOTHING";
|
||||
|
||||
?>
|
|
@ -0,0 +1,23 @@
|
|||
#!/bin/sh
|
||||
|
||||
COMMAND=update-metadatas
|
||||
|
||||
# lock to avoid concurrent updates
|
||||
LOCK=/var/run/univnautes-idp-$COMMAND.lock
|
||||
if [ -r $LOCK ]
|
||||
then
|
||||
PID=`cat $LOCK`
|
||||
echo "$COMMAND locked by $LOCK"
|
||||
ps waux | grep "$PID" | grep $COMMAND | grep -vq grep && exit
|
||||
echo "... but PID $PID is not a $COMMAND, continue"
|
||||
fi
|
||||
unlock() {
|
||||
rm -f $LOCK
|
||||
exit
|
||||
}
|
||||
trap unlock INT TERM EXIT
|
||||
echo $$ > $LOCK
|
||||
|
||||
cd /usr/local/univnautes/idp
|
||||
./manage.py $COMMAND
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# add_attributes_to_response signal
|
||||
import add_attributes
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
from authentic2.idp.signals import add_attributes_to_response
|
||||
import xml.etree.ElementTree as ET
|
||||
from hashlib import sha1
|
||||
from django.conf import settings
|
||||
import syslog
|
||||
|
||||
def add_attributes(request, user, audience, **kwargs):
|
||||
|
||||
# <saml:Attribute Name="urn:oid:2.16.840.1.113730.3.1.241"
|
||||
# NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
# FriendlyName="displayName">
|
||||
# <saml:AttributeValue>Jean Dupont</saml:AttributeValue>
|
||||
# </saml:Attribute>
|
||||
|
||||
displayName = ('urn:oid:2.16.840.1.113730.3.1.241',
|
||||
'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
|
||||
'displayName')
|
||||
displayName_value = user.displayname
|
||||
|
||||
# <saml:Attribute Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10"
|
||||
# NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
# FriendlyName="eduPersonTargetedID">
|
||||
# <saml:AttributeValue><saml:NameID
|
||||
# Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
||||
# NameQualifier="https://services-federation.renater.fr/test/idp"
|
||||
# SPNameQualifier="https://univnautes.entrouvert.lan/authsaml2/metadata">
|
||||
# C8NQsm1Y3Gas9m0AMDhxU7UxCSI=
|
||||
# </saml:NameID>
|
||||
# </saml:AttributeValue>
|
||||
# </saml:Attribute>
|
||||
|
||||
eduPersonTargetedID = ('urn:oid:1.3.6.1.4.1.5923.1.1.1.10',
|
||||
'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
|
||||
'eduPersonTargetedID')
|
||||
|
||||
pseudoid = sha1(user.username + audience + settings.SECRET_KEY).hexdigest()
|
||||
eduPersonTargetedID_value = ET.Element('saml:NameID')
|
||||
eduPersonTargetedID_value.attrib['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
|
||||
eduPersonTargetedID_value.attrib['NameQualifier'] = request.build_absolute_uri('/idp/saml2/metadata')
|
||||
eduPersonTargetedID_value.attrib['SPNameQualifier'] = audience
|
||||
eduPersonTargetedID_value.attrib['xmlns:saml'] = 'urn:oasis:names:tc:SAML:2.0:assertion'
|
||||
eduPersonTargetedID_value.text = pseudoid
|
||||
|
||||
privileges = getattr(user, 'univnautes_privileges', [])
|
||||
|
||||
syslog.openlog("idpauth", syslog.LOG_PID)
|
||||
syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO,
|
||||
'send user:%s to %s with eduPersonTargetedID:%s, privileges %s' %
|
||||
(user.username, audience, pseudoid, privileges))
|
||||
|
||||
return { 'attributes': {
|
||||
displayName: [displayName_value],
|
||||
eduPersonTargetedID: [eduPersonTargetedID_value],
|
||||
'univnautesPrivileges': privileges,
|
||||
} }
|
||||
|
||||
add_attributes_to_response.connect(add_attributes)
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
from django.contrib.auth.models import SiteProfileNotAvailable
|
||||
from authentic2.authsaml2.models import SAML2TransientUser
|
||||
import subprocess
|
||||
import syslog
|
||||
|
||||
#
|
||||
# Django auth backend
|
||||
#
|
||||
|
||||
class PfBackend:
|
||||
def authenticate(self, username=None, password=None):
|
||||
pfuser, privileges = pf_authenticate_user(username, password)
|
||||
if not pfuser:
|
||||
syslog.openlog("idpauth", syslog.LOG_PID)
|
||||
# FIXME: add details:
|
||||
# does not exists, bad password, expired account... ?
|
||||
syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO ,
|
||||
"FAIL: bad user/pass for %s" % username)
|
||||
return None
|
||||
if not 'univnautes-idp' in privileges:
|
||||
syslog.openlog("idpauth", syslog.LOG_PID)
|
||||
syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO ,
|
||||
"FAIL: user %s do not have IdP privilege" % username)
|
||||
return None
|
||||
return TransientUser(pfuser, privileges)
|
||||
|
||||
def get_user(self, user_id=None):
|
||||
pfuser, privileges = pf_get_user(user_id)
|
||||
if not pfuser or not 'univnautes-idp' in privileges:
|
||||
return None
|
||||
return TransientUser(pfuser, privileges)
|
||||
|
||||
|
||||
class TransientUser(SAML2TransientUser):
|
||||
'''mimics a django user'''
|
||||
|
||||
is_active = True
|
||||
is_staff = False
|
||||
univnautes_privilegess = tuple()
|
||||
|
||||
def __init__(self, pfuser, privileges):
|
||||
self.id = pfuser['username']
|
||||
self.pk = self.id
|
||||
self.displayname = pfuser['displayname'].decode('latin1')
|
||||
self.univnautes_privileges = privileges
|
||||
if privileges and 'univnautes-idp-admin' in privileges:
|
||||
self.is_staff = True
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s (%s)' % (self.displayname, self.username)
|
||||
|
||||
def get_profile(self):
|
||||
raise SiteProfileNotAvailable
|
||||
|
||||
def get_username(self):
|
||||
return self.id
|
||||
username = property(get_username)
|
||||
|
||||
#
|
||||
# get user from pfSense, via PHP script ../univnautes/bin/pf_auth
|
||||
#
|
||||
|
||||
def pf_authenticate_user(username, password):
|
||||
params = ['username=%s' % username, 'password=%s' % password]
|
||||
return pf_auth(params)
|
||||
|
||||
def pf_get_user(username):
|
||||
params = ['username=%s' % username]
|
||||
return pf_auth(params)
|
||||
|
||||
def pf_auth(params):
|
||||
cmd = ['/usr/local/univnautes/idp/idp/pf_auth', ] + params
|
||||
try:
|
||||
p = subprocess.Popen(cmd, close_fds=True,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
except OSError, e:
|
||||
syslog.openlog("idpauth", syslog.LOG_PID)
|
||||
syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO , "ERROR: OSError %s" % e)
|
||||
return None, None
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
return None, None
|
||||
user, privileges = eval(stdout)
|
||||
return user, privileges
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# UnivNautes
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
import os
|
||||
import sys
|
||||
from idp import pfconfigxml
|
||||
|
||||
def get(key, args):
|
||||
if key == 'idp':
|
||||
idp = pfconfigxml.get_idp()
|
||||
if idp is None:
|
||||
sys.exit(1)
|
||||
else:
|
||||
print idp
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'get infos from config.xml'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
action, key = args[0], args[1]
|
||||
except IndexError:
|
||||
print "syntax: configxml get|set key"
|
||||
return
|
||||
if action == 'get':
|
||||
return get(key, args)
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# UnivNautes
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management import call_command
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from idp import pfconfigxml
|
||||
import urllib2
|
||||
|
||||
from authentic2.saml.models import LibertyProvider
|
||||
from django.conf import settings
|
||||
|
||||
METADATAS_DIR = os.path.join('/', 'var', 'db', 'univnautes-idp-federations')
|
||||
|
||||
def metadata_filename(codename, suffix=None):
|
||||
if not suffix:
|
||||
return os.path.join(METADATAS_DIR, codename, 'metadata.xml')
|
||||
else:
|
||||
return os.path.join(METADATAS_DIR, codename, 'metadata.xml.%s' % suffix)
|
||||
|
||||
def store_metadata(codename, metadata):
|
||||
tempfilename = '/var/tmp/idp_%s-metadata.xml-%s' % (codename, os.getpid())
|
||||
f = open(tempfilename, 'wb')
|
||||
f.write(metadata)
|
||||
f.close()
|
||||
if not os.path.exists(METADATAS_DIR):
|
||||
os.mkdir(METADATAS_DIR, 0770)
|
||||
dir = os.path.join(METADATAS_DIR, codename)
|
||||
if not os.path.exists(dir):
|
||||
os.mkdir(dir, 0770)
|
||||
os.rename(tempfilename, metadata_filename(codename, 'downloaded'))
|
||||
|
||||
# remove obsoletes files if they exist (just to be clean...)
|
||||
for suffix in ('bad_signature', 'disabled'):
|
||||
try:
|
||||
os.remove(metadata_filename(codename, suffix))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def verify_metadata(codename, signcert):
|
||||
metadata = metadata_filename(codename, 'downloaded')
|
||||
if not signcert:
|
||||
print 'warn: do not verify %s metadata (no certificate provided)' % codename
|
||||
ret = True
|
||||
else:
|
||||
signcert_pem = metadata_filename(codename, 'signcert.pem')
|
||||
dir = os.path.join(METADATAS_DIR, codename)
|
||||
f = open(signcert_pem, 'wb')
|
||||
f.write(signcert)
|
||||
f.close()
|
||||
ret = 0 == subprocess.call(['xmlsec1', '--verify',
|
||||
'--id-attr:ID', 'EntitiesDescriptor',
|
||||
'--pubkey-cert-pem', signcert_pem,
|
||||
'--enabled-key-data', 'key-name',
|
||||
metadata])
|
||||
if ret:
|
||||
os.rename(metadata, metadata_filename(codename))
|
||||
else:
|
||||
print 'warn: bad signature for %s metadata' % codename
|
||||
os.rename(metadata, metadata_filename(codename, 'bad_signature'))
|
||||
return ret
|
||||
|
||||
def disable(codename):
|
||||
print 'disable %s metadata' % str(codename)
|
||||
LibertyProvider.objects.filter(federation_source=codename).delete()
|
||||
dir = os.path.join(METADATAS_DIR, codename)
|
||||
try:
|
||||
os.rename(metadata_filename(codename), metadata_filename(codename, 'disabled'))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'update all metadatas in %s' % METADATAS_DIR
|
||||
|
||||
def handle(self, *args, **options):
|
||||
actives = set()
|
||||
|
||||
federations = pfconfigxml.get_federations()
|
||||
for federation in federations:
|
||||
url = federation.get('url')
|
||||
metadata = federation.get('metadata')
|
||||
codename = federation.get('codename')
|
||||
descr = federation.get('descr')
|
||||
signcert = federation.get('signcert')
|
||||
|
||||
if not metadata:
|
||||
# get metadata from url
|
||||
try:
|
||||
print 'download federation %s metadata from url: %s' % (str(codename), str(url))
|
||||
metadata = urllib2.urlopen(url).read()
|
||||
except urllib2.HTTPError as e:
|
||||
metadata = None
|
||||
print 'Error loading metadata (%s)' % str(e)
|
||||
except urllib2.URLError as e:
|
||||
metadata = None
|
||||
print 'Error loading metadata (%s)' % str(e)
|
||||
else:
|
||||
print 'using local metadata for %s' % str(codename)
|
||||
|
||||
if metadata:
|
||||
store_metadata(codename, metadata)
|
||||
if verify_metadata(codename, signcert):
|
||||
try:
|
||||
call_command('sync-metadata', metadata_filename(codename),
|
||||
source=codename, sp=True, idp=False)
|
||||
except Exception, e:
|
||||
print 'sync-metadata aborted: %s' % str(e)
|
||||
print 'can not active metadata for %s' % str(codename)
|
||||
else:
|
||||
actives.add(codename)
|
||||
|
||||
present_in_filesystem = set(os.listdir(METADATAS_DIR))
|
||||
for codename in present_in_filesystem - actives:
|
||||
disable(codename)
|
||||
|
||||
present_in_database = set([str(i['federation_source']) \
|
||||
for i in LibertyProvider.objects.distinct().values('federation_source').order_by('federation_source')])
|
||||
for codename in present_in_database - actives:
|
||||
disable(codename)
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/local/bin/php -f
|
||||
<?php
|
||||
|
||||
/*
|
||||
* pf_auth username=.... : return a "python-formatted" user and its privileges:
|
||||
* ({'username':...,'displayname':....}, ('priv1','priv2',...))
|
||||
* with password=.... : only return the user if authenticate on pfsense
|
||||
*
|
||||
*/
|
||||
|
||||
array_shift($argv);
|
||||
foreach ($argv as $arg) {
|
||||
list($name, $value) = explode('=', $arg, 2);
|
||||
switch($name) {
|
||||
case 'username':
|
||||
$username=$value;
|
||||
break;
|
||||
case 'password':
|
||||
$password=$value;
|
||||
break;
|
||||
default:
|
||||
echo "BAD SYNTAX\n";
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
require("auth.inc");
|
||||
|
||||
if (isset($password)) {
|
||||
if (!authenticate_user($username, $password)) {
|
||||
echo "AUTH FAIL\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$user = getUserEntry($username);
|
||||
if (!$user) {
|
||||
echo "UNKNOWN USER\n";
|
||||
exit(3);
|
||||
}
|
||||
$k = array(
|
||||
"name" => "username",
|
||||
"descr" => "displayname",
|
||||
);
|
||||
echo "({";
|
||||
foreach($k as $key => $attr) {
|
||||
echo '"' . $attr . '":"""' . $user[$key] . '""", ';
|
||||
}
|
||||
echo "},";
|
||||
$privs = get_user_privileges($user);
|
||||
if ($privs) {
|
||||
echo "(";
|
||||
foreach($privs as $val) {
|
||||
echo "'$val', ";
|
||||
}
|
||||
echo ")";
|
||||
} else {
|
||||
echo "None";
|
||||
}
|
||||
echo ")";
|
||||
exit(0);
|
||||
|
||||
?>
|
|
@ -0,0 +1,138 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# UnivNautes
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from base64 import b64decode
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
def root():
|
||||
with open(settings.CONFIG_XML,'r') as f:
|
||||
x = ET.fromstring(f.read())
|
||||
return x
|
||||
|
||||
def laxint(s):
|
||||
try:
|
||||
return int(s)
|
||||
except:
|
||||
return 0
|
||||
|
||||
def get_ca(caref):
|
||||
for ca in root().findall('ca'):
|
||||
if ca.find('refid').text == caref:
|
||||
crt = ca.find('crt')
|
||||
if crt is not None:
|
||||
crt = crt.text.decode('base64')
|
||||
prv = ca.find('prv')
|
||||
if prv is not None:
|
||||
prv = prv.text.decode('base64')
|
||||
return {
|
||||
'caref': caref,
|
||||
'crt': crt,
|
||||
'prv': prv,
|
||||
}
|
||||
return None
|
||||
|
||||
def get_cert(certref):
|
||||
for cert in root().findall('cert'):
|
||||
if cert.find('refid').text == certref:
|
||||
crt = cert.find('crt')
|
||||
if crt is not None:
|
||||
crt = crt.text.decode('base64')
|
||||
prv = cert.find('prv')
|
||||
if prv is not None:
|
||||
prv = prv.text.decode('base64')
|
||||
caref = cert.find('caref')
|
||||
if caref is not None:
|
||||
caref = caref.text
|
||||
descr = cert.find('descr')
|
||||
if descr is not None:
|
||||
descr = descr.text
|
||||
return {
|
||||
'certref': caref,
|
||||
'descr': descr,
|
||||
'caref': caref,
|
||||
'crt': crt,
|
||||
'prv': prv,
|
||||
}
|
||||
return None
|
||||
|
||||
def get_idp():
|
||||
idp = root().find('univnautes/idp')
|
||||
if idp is None:
|
||||
return None
|
||||
|
||||
if idp.find('enable') is None:
|
||||
return None
|
||||
|
||||
certref = idp.find('certref')
|
||||
if certref is not None:
|
||||
saml_cert = get_cert(certref.text)
|
||||
else:
|
||||
saml_cert = None
|
||||
|
||||
return { 'saml_cert': saml_cert }
|
||||
|
||||
def get_federations():
|
||||
"""
|
||||
<federation>
|
||||
<enable/>
|
||||
<codename>renater_test</codename>
|
||||
<refid>fed_53d1161955a26</refid>
|
||||
<descr><![CDATA[Renater TEST Federation]]></descr>
|
||||
<url>https://federation.renater.fr/test/renater-test-metadata.xml</url>
|
||||
<metadata>[base64 encoded metadata]</metadata>
|
||||
<certref>53d115fac567b</certref>
|
||||
</federation>
|
||||
"""
|
||||
xml_federations = root().find('univnautes/federations')
|
||||
if xml_federations is None:
|
||||
return []
|
||||
xml_federations = xml_federations.findall('federation')
|
||||
federations = []
|
||||
for xml_federation in xml_federations:
|
||||
if xml_federation.find('enable') is None:
|
||||
continue
|
||||
codename = xml_federation.find('codename')
|
||||
if codename is not None:
|
||||
codename = codename.text
|
||||
url = xml_federation.find('url')
|
||||
if url is not None:
|
||||
url = url.text
|
||||
metadata = xml_federation.find('metadata')
|
||||
if metadata is not None:
|
||||
try:
|
||||
metadata = b64decode(metadata.text)
|
||||
except:
|
||||
metadata = None
|
||||
descr = xml_federation.find('descr')
|
||||
if descr is not None:
|
||||
descr = descr.text
|
||||
certref = xml_federation.find('certref')
|
||||
if certref is not None:
|
||||
signcert = (get_cert(certref.text) or {}).get('crt')
|
||||
else:
|
||||
signcert = None
|
||||
federations.append({
|
||||
'codename': codename,
|
||||
'url': url,
|
||||
'metadata': metadata,
|
||||
'signcert': signcert,
|
||||
'descr': descr,
|
||||
})
|
||||
return federations
|
|
@ -0,0 +1,252 @@
|
|||
# Django settings for idp project.
|
||||
|
||||
import os
|
||||
import pfconfigxml
|
||||
from django.conf import global_settings
|
||||
|
||||
PROJECT_PATH = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
DEBUG = os.environ.get('DEBUG') == 'yes'
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
# fastcgi (see http://docs.djangoproject.com/en/dev/howto/deployment/fastcgi/)
|
||||
FORCE_SCRIPT_NAME=''
|
||||
|
||||
ADMINS = ()
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': '/var/db/univnautes-idp.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
||||
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# In a Windows environment this must be set to your system time zone.
|
||||
TIME_ZONE = 'Europe/Paris'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'fr-fr'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
|
||||
# If you set this to False, Django will not format dates, numbers and
|
||||
# calendars according to the current locale.
|
||||
USE_L10N = True
|
||||
|
||||
# If you set this to False, Django will not use timezone-aware datetimes.
|
||||
USE_TZ = True
|
||||
|
||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||
# Example: "/var/www/example.com/media/"
|
||||
MEDIA_ROOT = ''
|
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://example.com/media/", "http://media.example.com/"
|
||||
MEDIA_URL = ''
|
||||
|
||||
# Absolute path to the directory static files should be collected to.
|
||||
# Don't put anything in this directory yourself; store your static files
|
||||
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
||||
# Example: "/var/www/example.com/static/"
|
||||
STATIC_ROOT = os.path.join(PROJECT_PATH, 'www', 'static')
|
||||
|
||||
# URL prefix for static files.
|
||||
# Example: "http://example.com/static/", "http://static.example.com/"
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
# Additional locations of static files
|
||||
STATICFILES_DIRS = (
|
||||
# Put strings here, like "/home/html/static" or "C:/www/django/static".
|
||||
# Always use forward slashes, even on Windows.
|
||||
# Don't forget to use absolute paths, not relative paths.
|
||||
)
|
||||
|
||||
# List of finder classes that know how to find static files in
|
||||
# various locations.
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
)
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY_FILENAME = os.path.join(PROJECT_PATH, 'secret.key')
|
||||
try:
|
||||
with open(SECRET_KEY_FILENAME, 'rb') as sk:
|
||||
SECRET_KEY = sk.read()
|
||||
except IOError:
|
||||
import random, string
|
||||
SECRET_KEY = "".join([random.SystemRandom().choice(string.digits + string.letters + string.punctuation) for i in range(100)])
|
||||
with open(SECRET_KEY_FILENAME, 'wb') as sk:
|
||||
sk.write(SECRET_KEY)
|
||||
|
||||
# List of callables to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'idp.template_loader.Loader',
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'authentic2.idp.middleware.DebugMiddleware'
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'idp.urls'
|
||||
|
||||
# Python dotted path to the WSGI application used by Django's runserver.
|
||||
WSGI_APPLICATION = 'idp.wsgi.application'
|
||||
|
||||
CPELEMENTS = '/var/db/cpelements'
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
CPELEMENTS,
|
||||
os.path.join(PROJECT_PATH, 'idp', 'templates'),
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'authentic2.saml',
|
||||
'authentic2.idp',
|
||||
'authentic2.attribute_aggregator',
|
||||
'authentic2.idp.saml',
|
||||
'authentic2.auth2_auth',
|
||||
'idp',
|
||||
'idp.users_admin',
|
||||
)
|
||||
|
||||
if DEBUG:
|
||||
INSTALLED_APPS += ('django.contrib.admin',)
|
||||
|
||||
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
||||
# it can't be 'django.contrib.sessions.serializers.JSONSerializer' with authentic2 (attributes)
|
||||
|
||||
SESSION_COOKIE_NAME = 'univnautes-idp-sessionid'
|
||||
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
|
||||
SESSION_FILE_PATH = '/var/tmp/univnautes-idp-sessions'
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
||||
SESSION_COOKIE_AGE = 36000 # one day of work
|
||||
SESSION_COOKIE_NAME = "pfidpsessionid"
|
||||
|
||||
try:
|
||||
os.mkdir(SESSION_FILE_PATH)
|
||||
except:
|
||||
pass
|
||||
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
LOGIN_URL = '/login'
|
||||
|
||||
# logging configuration
|
||||
# FIXME : syslog (freebsd -> /var/run/log) / local4 / debug
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'tmpidpfile': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.FileHandler',
|
||||
'filename': '/tmp/idp.log'
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['tmpidpfile'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# authentic2 settings (IDP)
|
||||
|
||||
# SAML2 IDP
|
||||
IDP_SAML2 = True
|
||||
IDP_BACKENDS = ('authentic2.idp.saml.backend.SamlBackend',)
|
||||
|
||||
LOCAL_METADATA_CACHE_TIMEOUT = 600
|
||||
SAML_METADATA_ROOT = 'metadata'
|
||||
SAML_METADATA_AUTOLOAD = 'none'
|
||||
|
||||
AUTH_FRONTENDS = ( 'authentic2.auth2_auth.backend.LoginPasswordBackend',)
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'idp.auth_backend.PfBackend',
|
||||
)
|
||||
|
||||
# pfidp use a specific auth backend, with a transient user
|
||||
# authentic2 can not (yet?) handle this case without :
|
||||
IDP_SAML2_AUTHN_CONTEXT_FROM_SESSION = False
|
||||
|
||||
# Default profile class
|
||||
AUTH_PROFILE_MODULE = 'idp.UserProfile'
|
||||
|
||||
# now get some values from config.xml
|
||||
# => server must be restarted if config.xml is changed
|
||||
|
||||
if 'CONFIG_XML' in os.environ:
|
||||
# for run this application outside a real pfSense
|
||||
CONFIG_XML = os.environ['CONFIG_XML']
|
||||
else:
|
||||
CONFIG_XML = '/cf/conf/config.xml'
|
||||
|
||||
idp = pfconfigxml.get_idp()
|
||||
# SAML certificate
|
||||
if idp:
|
||||
SAML_SIGNATURE_PUBLIC_KEY = idp.get('saml_cert', {}).get('crt')
|
||||
SAML_SIGNATURE_PRIVATE_KEY = idp.get('saml_cert', {}).get('prv')
|
||||
|
||||
#
|
||||
# univnautes parameters
|
||||
#
|
||||
|
||||
# store generated password here
|
||||
CLEAR_PASSWORD_DIR = '/var/db/univnautes/pfidp/passwords'
|
||||
# default expiration
|
||||
IDP_UA_DEFAULT_EXPIRES = 7
|
||||
|
||||
|
||||
#####
|
||||
|
||||
# useless but needed parameters...
|
||||
ACCOUNT_ACTIVATION_DAYS = 2
|
||||
EMAIL_HOST = 'localhost'
|
||||
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
|
||||
|
||||
# SSL Authentication
|
||||
AUTH_SSL = False
|
||||
SSLAUTH_CREATE_USER = False
|
||||
# SAML2 Authentication
|
||||
AUTH_SAML2 = False
|
||||
# OpenID Authentication
|
||||
AUTH_OPENID = False
|
||||
# OATH Authentication
|
||||
AUTH_OATH = False
|
||||
# OpenID settings
|
||||
IDP_OPENID = False
|
||||
# CAS settings
|
||||
IDP_CAS = False
|
||||
|
1109
usr/local/univnautes/idp/idp/static/bootstrap/css/bootstrap-responsive.css
vendored
Normal file
1109
usr/local/univnautes/idp/idp/static/bootstrap/css/bootstrap-responsive.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
usr/local/univnautes/idp/idp/static/bootstrap/css/bootstrap-responsive.min.css
vendored
Normal file
9
usr/local/univnautes/idp/idp/static/bootstrap/css/bootstrap-responsive.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,182 @@
|
|||
/*!
|
||||
* Datepicker for Bootstrap
|
||||
*
|
||||
* Copyright 2012 Stefan Petre
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*/
|
||||
.datepicker {
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 4px;
|
||||
margin-top: 1px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
/*.dow {
|
||||
border-top: 1px solid #ddd !important;
|
||||
}*/
|
||||
|
||||
}
|
||||
.datepicker:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 6px;
|
||||
}
|
||||
.datepicker:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #ffffff;
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 7px;
|
||||
}
|
||||
.datepicker > div {
|
||||
display: none;
|
||||
}
|
||||
.datepicker table {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.datepicker td,
|
||||
.datepicker th {
|
||||
text-align: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.datepicker td.day:hover {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.datepicker td.day.disabled {
|
||||
color: #eeeeee;
|
||||
}
|
||||
.datepicker td.old,
|
||||
.datepicker td.new {
|
||||
color: #999999;
|
||||
}
|
||||
.datepicker td.active,
|
||||
.datepicker td.active:hover {
|
||||
color: #ffffff;
|
||||
background-color: #006dcc;
|
||||
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
|
||||
border-color: #0044cc #0044cc #002a80;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
*background-color: #0044cc;
|
||||
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
|
||||
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker td.active:hover,
|
||||
.datepicker td.active:hover:hover,
|
||||
.datepicker td.active:focus,
|
||||
.datepicker td.active:hover:focus,
|
||||
.datepicker td.active:active,
|
||||
.datepicker td.active:hover:active,
|
||||
.datepicker td.active.active,
|
||||
.datepicker td.active:hover.active,
|
||||
.datepicker td.active.disabled,
|
||||
.datepicker td.active:hover.disabled,
|
||||
.datepicker td.active[disabled],
|
||||
.datepicker td.active:hover[disabled] {
|
||||
color: #ffffff;
|
||||
background-color: #0044cc;
|
||||
*background-color: #003bb3;
|
||||
}
|
||||
.datepicker td.active:active,
|
||||
.datepicker td.active:hover:active,
|
||||
.datepicker td.active.active,
|
||||
.datepicker td.active:hover.active {
|
||||
background-color: #003399 \9;
|
||||
}
|
||||
.datepicker td span {
|
||||
display: block;
|
||||
width: 47px;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
float: left;
|
||||
margin: 2px;
|
||||
cursor: pointer;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.datepicker td span:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.datepicker td span.active {
|
||||
color: #ffffff;
|
||||
background-color: #006dcc;
|
||||
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
|
||||
border-color: #0044cc #0044cc #002a80;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
*background-color: #0044cc;
|
||||
/* Darken IE7 buttons by default so they stand out more given they won't have borders */
|
||||
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker td span.active:hover,
|
||||
.datepicker td span.active:focus,
|
||||
.datepicker td span.active:active,
|
||||
.datepicker td span.active.active,
|
||||
.datepicker td span.active.disabled,
|
||||
.datepicker td span.active[disabled] {
|
||||
color: #ffffff;
|
||||
background-color: #0044cc;
|
||||
*background-color: #003bb3;
|
||||
}
|
||||
.datepicker td span.active:active,
|
||||
.datepicker td span.active.active {
|
||||
background-color: #003399 \9;
|
||||
}
|
||||
.datepicker td span.old {
|
||||
color: #999999;
|
||||
}
|
||||
.datepicker th.switch {
|
||||
width: 145px;
|
||||
}
|
||||
.datepicker th.next,
|
||||
.datepicker th.prev {
|
||||
font-size: 21px;
|
||||
}
|
||||
.datepicker thead tr:first-child th {
|
||||
cursor: pointer;
|
||||
}
|
||||
.datepicker thead tr:first-child th:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.input-append.date .add-on i,
|
||||
.input-prepend.date .add-on i {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,474 @@
|
|||
/* =========================================================
|
||||
* bootstrap-datepicker.js
|
||||
* http://www.eyecon.ro/bootstrap-datepicker
|
||||
* =========================================================
|
||||
* Copyright 2012 Stefan Petre
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ========================================================= */
|
||||
|
||||
!function( $ ) {
|
||||
|
||||
// Picker object
|
||||
|
||||
var Datepicker = function(element, options){
|
||||
this.element = $(element);
|
||||
this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
|
||||
this.picker = $(DPGlobal.template)
|
||||
.appendTo('body')
|
||||
.on({
|
||||
click: $.proxy(this.click, this)//,
|
||||
//mousedown: $.proxy(this.mousedown, this)
|
||||
});
|
||||
this.isInput = this.element.is('input');
|
||||
this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
|
||||
|
||||
if (this.isInput) {
|
||||
this.element.on({
|
||||
focus: $.proxy(this.show, this),
|
||||
//blur: $.proxy(this.hide, this),
|
||||
keyup: $.proxy(this.update, this)
|
||||
});
|
||||
} else {
|
||||
if (this.component){
|
||||
this.component.on('click', $.proxy(this.show, this));
|
||||
} else {
|
||||
this.element.on('click', $.proxy(this.show, this));
|
||||
}
|
||||
}
|
||||
|
||||
this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
|
||||
if (typeof this.minViewMode === 'string') {
|
||||
switch (this.minViewMode) {
|
||||
case 'months':
|
||||
this.minViewMode = 1;
|
||||
break;
|
||||
case 'years':
|
||||
this.minViewMode = 2;
|
||||
break;
|
||||
default:
|
||||
this.minViewMode = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
|
||||
if (typeof this.viewMode === 'string') {
|
||||
switch (this.viewMode) {
|
||||
case 'months':
|
||||
this.viewMode = 1;
|
||||
break;
|
||||
case 'years':
|
||||
this.viewMode = 2;
|
||||
break;
|
||||
default:
|
||||
this.viewMode = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.startViewMode = this.viewMode;
|
||||
this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
|
||||
this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
|
||||
this.onRender = options.onRender;
|
||||
this.fillDow();
|
||||
this.fillMonths();
|
||||
this.update();
|
||||
this.showMode();
|
||||
};
|
||||
|
||||
Datepicker.prototype = {
|
||||
constructor: Datepicker,
|
||||
|
||||
show: function(e) {
|
||||
this.picker.show();
|
||||
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
|
||||
this.place();
|
||||
$(window).on('resize', $.proxy(this.place, this));
|
||||
if (e ) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
if (!this.isInput) {
|
||||
}
|
||||
var that = this;
|
||||
$(document).on('mousedown', function(ev){
|
||||
if ($(ev.target).closest('.datepicker').length == 0) {
|
||||
that.hide();
|
||||
}
|
||||
});
|
||||
this.element.trigger({
|
||||
type: 'show',
|
||||
date: this.date
|
||||
});
|
||||
},
|
||||
|
||||
hide: function(){
|
||||
this.picker.hide();
|
||||
$(window).off('resize', this.place);
|
||||
this.viewMode = this.startViewMode;
|
||||
this.showMode();
|
||||
if (!this.isInput) {
|
||||
$(document).off('mousedown', this.hide);
|
||||
}
|
||||
//this.set();
|
||||
this.element.trigger({
|
||||
type: 'hide',
|
||||
date: this.date
|
||||
});
|
||||
},
|
||||
|
||||
set: function() {
|
||||
var formated = DPGlobal.formatDate(this.date, this.format);
|
||||
if (!this.isInput) {
|
||||
if (this.component){
|
||||
this.element.find('input').prop('value', formated);
|
||||
}
|
||||
this.element.data('date', formated);
|
||||
} else {
|
||||
this.element.prop('value', formated);
|
||||
}
|
||||
},
|
||||
|
||||
setValue: function(newDate) {
|
||||
if (typeof newDate === 'string') {
|
||||
this.date = DPGlobal.parseDate(newDate, this.format);
|
||||
} else {
|
||||
this.date = new Date(newDate);
|
||||
}
|
||||
this.set();
|
||||
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
|
||||
this.fill();
|
||||
},
|
||||
|
||||
place: function(){
|
||||
var offset = this.component ? this.component.offset() : this.element.offset();
|
||||
this.picker.css({
|
||||
top: offset.top + this.height,
|
||||
left: offset.left
|
||||
});
|
||||
},
|
||||
|
||||
update: function(newDate){
|
||||
this.date = DPGlobal.parseDate(
|
||||
typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
|
||||
this.format
|
||||
);
|
||||
this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
|
||||
this.fill();
|
||||
},
|
||||
|
||||
fillDow: function(){
|
||||
var dowCnt = this.weekStart;
|
||||
var html = '<tr>';
|
||||
while (dowCnt < this.weekStart + 7) {
|
||||
html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
|
||||
}
|
||||
html += '</tr>';
|
||||
this.picker.find('.datepicker-days thead').append(html);
|
||||
},
|
||||
|
||||
fillMonths: function(){
|
||||
var html = '';
|
||||
var i = 0
|
||||
while (i < 12) {
|
||||
html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
|
||||
}
|
||||
this.picker.find('.datepicker-months td').append(html);
|
||||
},
|
||||
|
||||
fill: function() {
|
||||
var d = new Date(this.viewDate),
|
||||
year = d.getFullYear(),
|
||||
month = d.getMonth(),
|
||||
currentDate = this.date.valueOf();
|
||||
this.picker.find('.datepicker-days th:eq(1)')
|
||||
.text(DPGlobal.dates.months[month]+' '+year);
|
||||
var prevMonth = new Date(year, month-1, 28,0,0,0,0),
|
||||
day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
|
||||
prevMonth.setDate(day);
|
||||
prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
|
||||
var nextMonth = new Date(prevMonth);
|
||||
nextMonth.setDate(nextMonth.getDate() + 42);
|
||||
nextMonth = nextMonth.valueOf();
|
||||
var html = [];
|
||||
var clsName,
|
||||
prevY,
|
||||
prevM;
|
||||
while(prevMonth.valueOf() < nextMonth) {
|
||||
if (prevMonth.getDay() === this.weekStart) {
|
||||
html.push('<tr>');
|
||||
}
|
||||
clsName = this.onRender(prevMonth);
|
||||
prevY = prevMonth.getFullYear();
|
||||
prevM = prevMonth.getMonth();
|
||||
if ((prevM < month && prevY === year) || prevY < year) {
|
||||
clsName += ' old';
|
||||
} else if ((prevM > month && prevY === year) || prevY > year) {
|
||||
clsName += ' new';
|
||||
}
|
||||
if (prevMonth.valueOf() === currentDate) {
|
||||
clsName += ' active';
|
||||
}
|
||||
html.push('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>');
|
||||
if (prevMonth.getDay() === this.weekEnd) {
|
||||
html.push('</tr>');
|
||||
}
|
||||
prevMonth.setDate(prevMonth.getDate()+1);
|
||||
}
|
||||
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
|
||||
var currentYear = this.date.getFullYear();
|
||||
|
||||
var months = this.picker.find('.datepicker-months')
|
||||
.find('th:eq(1)')
|
||||
.text(year)
|
||||
.end()
|
||||
.find('span').removeClass('active');
|
||||
if (currentYear === year) {
|
||||
months.eq(this.date.getMonth()).addClass('active');
|
||||
}
|
||||
|
||||
html = '';
|
||||
year = parseInt(year/10, 10) * 10;
|
||||
var yearCont = this.picker.find('.datepicker-years')
|
||||
.find('th:eq(1)')
|
||||
.text(year + '-' + (year + 9))
|
||||
.end()
|
||||
.find('td');
|
||||
year -= 1;
|
||||
for (var i = -1; i < 11; i++) {
|
||||
html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
|
||||
year += 1;
|
||||
}
|
||||
yearCont.html(html);
|
||||
},
|
||||
|
||||
click: function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var target = $(e.target).closest('span, td, th');
|
||||
if (target.length === 1) {
|
||||
switch(target[0].nodeName.toLowerCase()) {
|
||||
case 'th':
|
||||
switch(target[0].className) {
|
||||
case 'switch':
|
||||
this.showMode(1);
|
||||
break;
|
||||
case 'prev':
|
||||
case 'next':
|
||||
this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
|
||||
this.viewDate,
|
||||
this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) +
|
||||
DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
|
||||
);
|
||||
this.fill();
|
||||
this.set();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'span':
|
||||
if (target.is('.month')) {
|
||||
var month = target.parent().find('span').index(target);
|
||||
this.viewDate.setMonth(month);
|
||||
} else {
|
||||
var year = parseInt(target.text(), 10)||0;
|
||||
this.viewDate.setFullYear(year);
|
||||
}
|
||||
if (this.viewMode !== 0) {
|
||||
this.date = new Date(this.viewDate);
|
||||
this.element.trigger({
|
||||
type: 'changeDate',
|
||||
date: this.date,
|
||||
viewMode: DPGlobal.modes[this.viewMode].clsName
|
||||
});
|
||||
}
|
||||
this.showMode(-1);
|
||||
this.fill();
|
||||
this.set();
|
||||
break;
|
||||
case 'td':
|
||||
if (target.is('.day') && !target.is('.disabled')){
|
||||
var day = parseInt(target.text(), 10)||1;
|
||||
var month = this.viewDate.getMonth();
|
||||
if (target.is('.old')) {
|
||||
month -= 1;
|
||||
} else if (target.is('.new')) {
|
||||
month += 1;
|
||||
}
|
||||
var year = this.viewDate.getFullYear();
|
||||
this.date = new Date(year, month, day,0,0,0,0);
|
||||
this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
|
||||
this.fill();
|
||||
this.set();
|
||||
this.element.trigger({
|
||||
type: 'changeDate',
|
||||
date: this.date,
|
||||
viewMode: DPGlobal.modes[this.viewMode].clsName
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mousedown: function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
showMode: function(dir) {
|
||||
if (dir) {
|
||||
this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
|
||||
}
|
||||
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.datepicker = function ( option, val ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this),
|
||||
data = $this.data('datepicker'),
|
||||
options = typeof option === 'object' && option;
|
||||
if (!data) {
|
||||
$this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
|
||||
}
|
||||
if (typeof option === 'string') data[option](val);
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.datepicker.defaults = {
|
||||
onRender: function(date) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
$.fn.datepicker.Constructor = Datepicker;
|
||||
|
||||
var DPGlobal = {
|
||||
modes: [
|
||||
{
|
||||
clsName: 'days',
|
||||
navFnc: 'Month',
|
||||
navStep: 1
|
||||
},
|
||||
{
|
||||
clsName: 'months',
|
||||
navFnc: 'FullYear',
|
||||
navStep: 1
|
||||
},
|
||||
{
|
||||
clsName: 'years',
|
||||
navFnc: 'FullYear',
|
||||
navStep: 10
|
||||
}],
|
||||
dates:{
|
||||
days: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"],
|
||||
daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"],
|
||||
daysMin: ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"],
|
||||
months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
|
||||
monthsShort: ["Jan", "Fév", "Mar", "Avr", "Mai", "Jui", "Jul", "Aoû", "Sep", "Oct", "Nov", "Déc"]
|
||||
},
|
||||
isLeapYear: function (year) {
|
||||
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
|
||||
},
|
||||
getDaysInMonth: function (year, month) {
|
||||
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
|
||||
},
|
||||
parseFormat: function(format){
|
||||
var separator = format.match(/[.\/\-\s].*?/),
|
||||
parts = format.split(/\W+/);
|
||||
if (!separator || !parts || parts.length === 0){
|
||||
throw new Error("Invalid date format.");
|
||||
}
|
||||
return {separator: separator, parts: parts};
|
||||
},
|
||||
parseDate: function(date, format) {
|
||||
var parts = date.split(format.separator),
|
||||
date = new Date(),
|
||||
val;
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
date.setSeconds(0);
|
||||
date.setMilliseconds(0);
|
||||
if (parts.length === format.parts.length) {
|
||||
var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
|
||||
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
|
||||
val = parseInt(parts[i], 10)||1;
|
||||
switch(format.parts[i]) {
|
||||
case 'dd':
|
||||
case 'd':
|
||||
day = val;
|
||||
date.setDate(val);
|
||||
break;
|
||||
case 'mm':
|
||||
case 'm':
|
||||
month = val - 1;
|
||||
date.setMonth(val - 1);
|
||||
break;
|
||||
case 'yy':
|
||||
year = 2000 + val;
|
||||
date.setFullYear(2000 + val);
|
||||
break;
|
||||
case 'yyyy':
|
||||
year = val;
|
||||
date.setFullYear(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
date = new Date(year, month, day, 0 ,0 ,0);
|
||||
}
|
||||
return date;
|
||||
},
|
||||
formatDate: function(date, format){
|
||||
var val = {
|
||||
d: date.getDate(),
|
||||
m: date.getMonth() + 1,
|
||||
yy: date.getFullYear().toString().substring(2),
|
||||
yyyy: date.getFullYear()
|
||||
};
|
||||
val.dd = (val.d < 10 ? '0' : '') + val.d;
|
||||
val.mm = (val.m < 10 ? '0' : '') + val.m;
|
||||
var date = [];
|
||||
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
|
||||
date.push(val[format.parts[i]]);
|
||||
}
|
||||
return date.join(format.separator);
|
||||
},
|
||||
headTemplate: '<thead>'+
|
||||
'<tr>'+
|
||||
'<th class="prev">‹</th>'+
|
||||
'<th colspan="5" class="switch"></th>'+
|
||||
'<th class="next">›</th>'+
|
||||
'</tr>'+
|
||||
'</thead>',
|
||||
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
|
||||
};
|
||||
DPGlobal.template = '<div class="datepicker dropdown-menu">'+
|
||||
'<div class="datepicker-days">'+
|
||||
'<table class=" table-condensed">'+
|
||||
DPGlobal.headTemplate+
|
||||
'<tbody></tbody>'+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'<div class="datepicker-months">'+
|
||||
'<table class="table-condensed">'+
|
||||
DPGlobal.headTemplate+
|
||||
DPGlobal.contTemplate+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'<div class="datepicker-years">'+
|
||||
'<table class="table-condensed">'+
|
||||
DPGlobal.headTemplate+
|
||||
DPGlobal.contTemplate+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'</div>';
|
||||
|
||||
}( window.jQuery );
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,152 @@
|
|||
// Stupid jQuery table plugin.
|
||||
|
||||
// Call on a table
|
||||
// sortFns: Sort functions for your datatypes.
|
||||
(function($) {
|
||||
|
||||
$.fn.stupidtable = function(sortFns) {
|
||||
return this.each(function() {
|
||||
var $table = $(this);
|
||||
sortFns = sortFns || {};
|
||||
|
||||
// ==================================================== //
|
||||
// Utility functions //
|
||||
// ==================================================== //
|
||||
|
||||
// Merge sort functions with some default sort functions.
|
||||
sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
|
||||
|
||||
// Return the resulting indexes of a sort so we can apply
|
||||
// this result elsewhere. This returns an array of index numbers.
|
||||
// return[0] = x means "arr's 0th element is now at x"
|
||||
var sort_map = function(arr, sort_function) {
|
||||
var map = [];
|
||||
var index = 0;
|
||||
var sorted = arr.slice(0).sort(sort_function);
|
||||
for (var i=0; i<arr.length; i++) {
|
||||
index = $.inArray(arr[i], sorted);
|
||||
|
||||
// If this index is already in the map, look for the next index.
|
||||
// This handles the case of duplicate entries.
|
||||
while ($.inArray(index, map) != -1) {
|
||||
index++;
|
||||
}
|
||||
map.push(index);
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
// Apply a sort map to the array.
|
||||
var apply_sort_map = function(arr, map) {
|
||||
var clone = arr.slice(0),
|
||||
newIndex = 0;
|
||||
for (var i=0; i<map.length; i++) {
|
||||
newIndex = map[i];
|
||||
clone[newIndex] = arr[i];
|
||||
}
|
||||
return clone;
|
||||
};
|
||||
|
||||
// ==================================================== //
|
||||
// Begin execution! //
|
||||
// ==================================================== //
|
||||
|
||||
// Do sorting when THs are clicked
|
||||
$table.on("click", "th", function() {
|
||||
var trs = $table.children("tbody").children("tr");
|
||||
var $this = $(this);
|
||||
var th_index = 0;
|
||||
var dir = $.fn.stupidtable.dir;
|
||||
|
||||
$table.find("th").slice(0, $this.index()).each(function() {
|
||||
var cols = $(this).attr("colspan") || 1;
|
||||
th_index += parseInt(cols,10);
|
||||
});
|
||||
|
||||
// Determine (and/or reverse) sorting direction, default `asc`
|
||||
var sort_dir = $this.data("sort-default") || dir.ASC;
|
||||
if ($this.data("sort-dir"))
|
||||
sort_dir = $this.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
|
||||
|
||||
// Choose appropriate sorting function.
|
||||
var type = $this.data("sort") || null;
|
||||
|
||||
// Prevent sorting if no type defined
|
||||
if (type === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger `beforetablesort` event that calling scripts can hook into;
|
||||
// pass parameters for sorted column index and sorting direction
|
||||
$table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
|
||||
// More reliable method of forcing a redraw
|
||||
$table.css("display");
|
||||
|
||||
// Run sorting asynchronously on a timout to force browser redraw after
|
||||
// `beforetablesort` callback. Also avoids locking up the browser too much.
|
||||
setTimeout(function() {
|
||||
// Gather the elements for this column
|
||||
var column = [];
|
||||
var sortMethod = sortFns[type];
|
||||
|
||||
// Push either the value of the `data-order-by` attribute if specified
|
||||
// or just the text() value in this column to column[] for comparison.
|
||||
trs.each(function(index,tr) {
|
||||
var $e = $(tr).children().eq(th_index);
|
||||
var sort_val = $e.data("sort-value");
|
||||
var order_by = typeof(sort_val) !== "undefined" ? sort_val : $e.text();
|
||||
column.push(order_by);
|
||||
});
|
||||
|
||||
// Create the sort map. This column having a sort-dir implies it was
|
||||
// the last column sorted. As long as no data-sort-desc is specified,
|
||||
// we're free to just reverse the column.
|
||||
var theMap;
|
||||
if (sort_dir == dir.ASC)
|
||||
theMap = sort_map(column, sortMethod);
|
||||
else
|
||||
theMap = sort_map(column, function(a, b) { return -sortMethod(a, b); });
|
||||
|
||||
// Reset siblings
|
||||
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
|
||||
$this.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
|
||||
|
||||
var sortedTRs = $(apply_sort_map(trs, theMap));
|
||||
$table.children("tbody").remove();
|
||||
$table.append("<tbody />").append(sortedTRs);
|
||||
|
||||
// Trigger `aftertablesort` event. Similar to `beforetablesort`
|
||||
$table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
|
||||
// More reliable method of forcing a redraw
|
||||
$table.css("display");
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Enum containing sorting directions
|
||||
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
|
||||
|
||||
$.fn.stupidtable.default_sort_fns = {
|
||||
"int": function(a, b) {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
},
|
||||
"float": function(a, b) {
|
||||
return parseFloat(a) - parseFloat(b);
|
||||
},
|
||||
"string": function(a, b) {
|
||||
if (a < b) return -1;
|
||||
if (a > b) return +1;
|
||||
return 0;
|
||||
},
|
||||
"string-ins": function(a, b) {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
if (a < b) return -1;
|
||||
if (a > b) return +1;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -0,0 +1,10 @@
|
|||
import os
|
||||
from django.conf import settings
|
||||
from django.template.loaders.filesystem import Loader as FileSystemLoader
|
||||
|
||||
class Loader(FileSystemLoader):
|
||||
|
||||
def get_template_sources(self, template_name, template_dirs=None):
|
||||
filename = os.path.join(settings.CPELEMENTS,
|
||||
'captiveportal-idp-template-%s' % template_name.replace('/', '-'))
|
||||
return (filename,)
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %} {% trans "Error: page not found" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>{% trans "Error: page not found" %}</h2>
|
||||
|
||||
<p>{% trans "The page you requested has not been found on this server." %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %} {% trans "Error: server error (500)" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>{% trans "Server Error" %}</h2>
|
||||
|
||||
<p>{% trans "We're sorry but a server error has occurred." %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,10 @@
|
|||
{% extends "admin/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }} | {% trans 'Authentic site admin' %}{% endblock %}
|
||||
|
||||
{% block branding %}
|
||||
<h1 id="site-name">{% trans 'Authentic administration' %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block nav-global %}{% endblock %}
|
|
@ -0,0 +1,45 @@
|
|||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %} {% trans "Logs" %} {% endblock %}
|
||||
{% block nav-global %}{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/admin/">{% trans "Home" %}</a> > Admin_log_view</div>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>{% trans "Logs page" %}</h2>
|
||||
|
||||
{% if not file %}
|
||||
<p> There is no log for the moment.</p>
|
||||
{% else %}
|
||||
{% for log in logs.object_list %}
|
||||
{% if "CRITICAL" in log %}
|
||||
<p> <strong> {{ log }} </strong> </p>
|
||||
{% else %}
|
||||
{% if "ERROR" in log %}
|
||||
<p> <strong> {{ log }} </strong> </p>
|
||||
{% else %}
|
||||
<p> {{ log }} </p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class = "pagination">
|
||||
<span class = "step-links">
|
||||
{% if logs.has_previous %}
|
||||
<a href = "?page={{ logs.previous_page_number }}">previous</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="current">
|
||||
Page {{ logs.number }} of {{ logs.paginator.num_pages }}.
|
||||
</span>
|
||||
|
||||
{% if logs.has_next %}
|
||||
<a href="?page={{ logs.next_page_number }}">next</a>
|
||||
{% endif %}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% trans "Log in" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% for name, content in methods %}
|
||||
{{ content|safe }}
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,32 @@
|
|||
{% load i18n %}
|
||||
<form method="post" action="" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if form.non_field_errors %}
|
||||
{% for error in form.non_field_errors %}
|
||||
<p><strong>{{ error }}</strong></p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="id_username">{% trans "Username" %}
|
||||
{% for error in form.username.errors %}<br /><i class="icon-warning-sign"></i> <font color="red">{{ error|escape }}</font>{% endfor %}
|
||||
</label>
|
||||
<div class="controls">
|
||||
{{ form.username }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="id_password">{% trans "Password" %}
|
||||
{% for error in form.password.errors %}<br /><i class="icon-warning-sign"></i> <font color="red">{{ error|escape }}</font>{% endfor %}
|
||||
</label>
|
||||
<div class="controls">
|
||||
{{ form.password }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group"><div class="controls">
|
||||
<input type="submit" name="{{ submit_name }}" value="{% trans "Log in" %}" class="btn"/>
|
||||
</div></div>
|
||||
</form>
|
|
@ -0,0 +1,85 @@
|
|||
{% load i18n %}{% load staticfiles %}<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>eduspot :: {% block title %}{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="eduspot / IdP local">
|
||||
<meta name="author" content="UNPIdF - Entr'ouvert www.entrouvert.com">
|
||||
|
||||
|
||||
<!-- Le styles -->
|
||||
<link href="{% static "bootstrap/css/bootstrap.css" %}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
body { padding-top: 60px; padding-bottom: 40px; }
|
||||
.sidebar-nav { padding: 9px 0; }
|
||||
</style>
|
||||
<link href="{% static "bootstrap/css/bootstrap-responsive.css" %}" rel="stylesheet">
|
||||
|
||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="{% static "html5shiv/js/html5shiv.js" %}"></script>
|
||||
<![endif]-->
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
|
||||
<!-- Fav and touch icons -->
|
||||
<link rel="shortcut icon" href="{% static "univnautes/img/favicon.ico" %}">
|
||||
|
||||
<!-- local CSS -->
|
||||
<link rel="stylesheet" href="{% static "univnautes/css/univnautes.css" %}"/>
|
||||
<!--[if lte IE 8]><link rel="stylesheet" href="{% static "univnautes/css/univnautes.ie.css" %}"/><![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body {% block body_args %}{% endblock %} {% block bodyargs %}{% endblock %}>
|
||||
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</a>
|
||||
<a class="brand" href="/"><i class="icon-eduspot"></i> Accès eduspot</a>
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
<!-- li><a href="/conditions">Conditions d'utilisation</a></li -->
|
||||
{% if mailform %}<li><a href="/mail">Contact <i class="icon-envelope icon-white"></i></a></li>{% endif %}
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row-fluid">
|
||||
{% block header %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
{% block disclaimer %}{% endblock %}
|
||||
</div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<hr>
|
||||
|
||||
<footer>
|
||||
<p>© UNPIdF 2014
|
||||
{% block footer %}{% endblock %}
|
||||
</footer>
|
||||
|
||||
</div><!--/.fluid-container-->
|
||||
|
||||
<!-- Le javascript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="{% static "jquery/js/jquery-1.10.2.min.js" %}"></script>
|
||||
<script src="{% static "bootstrap/js/bootstrap.min.js" %}"></script>
|
||||
{% block end %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block bodyargs %}onload="setTimeout(function () { window.location='{{ next_page }}' }, {{ redir_timeout }})"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ title }}</h1>
|
||||
{{ msg }}
|
||||
<p><a href="{{ back }}">{% trans "Back" %}<a/></p>
|
||||
{% endblock %}
|
|
@ -0,0 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %} {% trans "Error: authentication failure" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>{% trans "Authentication failure" %}</h2>
|
||||
|
||||
<p>{% trans "The SSL authentication has failed" %}</p>
|
||||
<a href="/">Back</a>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% trans "Authentic - Account Management" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Account Management" %}</h2>
|
||||
<h3>{% trans "Profile" %}</h3>
|
||||
<div id="profile">
|
||||
{% if profile %}
|
||||
<dl>
|
||||
{% for key, value in profile %}
|
||||
<dt>{{ key|capfirst }}</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
<p> <a href="{% url "profiles_edit_profile" %}">{% trans "Edit profile" %}</a></p>
|
||||
{% else %}
|
||||
<p> <a href="{% url "profiles_create_profile" %}">{% trans "Create profile" %}</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h3>{% trans "Credentials" %}</h3>
|
||||
{% for html_block in frontends_block %}
|
||||
{{ html_block|safe }}
|
||||
{% endfor %}
|
||||
<p><a href="/">{% trans "Back" %}<a/></p>
|
||||
{% endblock %}
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% trans "Authentic2 connected" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>
|
||||
Connecté en tant que « {{ user.username }} »
|
||||
</h2>
|
||||
|
||||
<h3>{% if user.displayname %}{{ user.displayname }}{% endif %}</h3>
|
||||
{% if user.is_staff %}
|
||||
<a class="btn btn-success" id="users_admin" href="/users-admin/"><i class="icon-wrench"></i> Gestion des invités</a>
|
||||
{% endif %}
|
||||
<a class="btn" id="logout" href="{% url "auth_logout" %}"><i class="icon-off"></i> {% trans "Log out" %}</a>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% trans "Logout" %}
|
||||
{% endblock %}
|
||||
{% block bodyargs %}onload="setTimeout(function () { window.location='{{ next_page }}' }, {{ redir_timeout }})"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans message %}</h1>
|
||||
<ul class="logout-list">
|
||||
{% for fragment in logout_list %}
|
||||
{{ fragment|safe }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div id="continue-link"><a href="{{ next_page }}">{% trans "Continuer" %}</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,17 @@
|
|||
{% load i18n %}{% load staticfiles %}<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" href="{% static "css/style.css" %}" />
|
||||
<title>{% block title %}User test{% endblock %}</title>
|
||||
{% block extra_scripts %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body id="iframe" {% block bodyargs %}{% endblock %} >
|
||||
<div style="width: 400px; height: 200px; margin: auto">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,37 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %} {% trans "Consent page" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
{% load i18n %}
|
||||
<div id="consent">
|
||||
<p>
|
||||
{% if provider_id %}
|
||||
{% trans "Do you accept to federate your account with " %} <strong>{{ provider_id }}</strong> ?
|
||||
{% else %}
|
||||
{% trans "Do you accept to federate your account ?" %}
|
||||
{% endif %}
|
||||
{{ provider_id2 }}
|
||||
</p>
|
||||
<p>
|
||||
{% if attributes %}
|
||||
{% trans "Do you accept to send these attributes?" %}
|
||||
<ul>
|
||||
{% for attribute in attributes %}
|
||||
<li>{{ attribute }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</p>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}" />
|
||||
<input type="submit" name="accept" value="{% trans 'Accept' %}"/>
|
||||
<input type="submit" name="refuse" value="{% trans 'Refuse' %}"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %} {% trans "Consent page for attribute propagation" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
{% load i18n %}
|
||||
<div id="consent">
|
||||
<p>
|
||||
{% if attributes %}
|
||||
{% if provider_id %}
|
||||
{% trans "Do you accept to send these attributes to " %} <strong>{{ provider_id }}</strong> ?
|
||||
{% else %}
|
||||
{% trans "Do you accept to send these attributes?" %}
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% for attribute in attributes %}
|
||||
<li>{{ attribute }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</p>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}" />
|
||||
<input type="submit" name="accept" value="{% trans 'Accept' %}"/>
|
||||
<input type="submit" name="refuse" value="{% trans 'Refuse' %}"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,27 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %} {% trans "Consent page for federation" %} {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
{% load i18n %}
|
||||
<div id="consent">
|
||||
<p>
|
||||
{% if provider_id %}
|
||||
{% trans "Do you accept to federate your account with " %} <strong>{{ provider_id }}</strong> ?
|
||||
{% else %}
|
||||
{% trans "Do you accept to federate your account ?" %}
|
||||
{% endif %}
|
||||
{{ provider_id2 }}
|
||||
</p>
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}" />
|
||||
<input type="submit" name="accept" value="{% trans 'Accept' %}"/>
|
||||
<input type="submit" name="refuse" value="{% trans 'Refuse' %}"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<!-- Close any popup enclosing us -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>Redirect to {{ next }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
window.open('{{ next }}', '_top');
|
||||
</script>
|
||||
</body>
|
|
@ -0,0 +1,66 @@
|
|||
{% load staticfiles %}<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>UnivNautes IdP / Gestion des utilisateurs / {% block title %}{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<link href="{% static "bootstrap/css/bootstrap.css" %}" rel="stylesheet">
|
||||
<style>
|
||||
body { padding-top: 60px; }
|
||||
th { text-align: right; padding-right: 20px; vertical-align: top; }
|
||||
.disabled { color: #ccc; }
|
||||
ul.errorlist { list-style-type: none; margin: 0; }
|
||||
ul.errorlist li { color: #f00; }
|
||||
.helptext { font-size: 0.8em; font-style: italic; }
|
||||
</style>
|
||||
<link href="{% static "bootstrap/css/bootstrap-responsive.css" %}" rel="stylesheet">
|
||||
<link href="{% static "bootstrap/css/datepicker.css" %}" rel="stylesheet">
|
||||
|
||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="{% static "html5shiv/js/html5shiv.js" %}></script>
|
||||
<![endif]-->
|
||||
|
||||
<script type="text/javascript" src="{% static "jquery/js/jquery-1.10.2.min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.min.js" %}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<a class="brand" href="/users-admin/">UnivNautes IdP</a>
|
||||
<div class="btn-group pull-right"><a href="/logout" class="btn"><i class="icon-off"></i> Déconnexion</a></div>
|
||||
<div class="nav">
|
||||
<ul class="nav">{% block nav %}{% endblock %}</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
{% block title_content %}{% endblock %}
|
||||
|
||||
{% block messages %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-block">
|
||||
<a class="close" data-dismiss="alert" href="#">×</a>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<script>$(".alert").alert();</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,49 @@
|
|||
{% extends "users_admin/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<br />
|
||||
|
||||
<p>
|
||||
{{ text }}
|
||||
</p>
|
||||
|
||||
{% if users %}
|
||||
<table class="table table-condensed table-striped" id="usersTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="type-string">login</th>
|
||||
<th class="type-string">expiration</th>
|
||||
<th class="type-string">nom complet</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ user.name }}
|
||||
{% if 'univnautes-idp-multiple' in user.priv %}(multiple){% endif %}
|
||||
</td>
|
||||
<td>{{ user.expires|default:"jamais" }} ({{ user.ttl }})</td>
|
||||
<td>{{ user.descr }}</td>
|
||||
</tr
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<form action="" method="post" autocomplete="off">
|
||||
{% csrf_token %}
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="Confirmer" class="btn btn-primary" />
|
||||
<a href="/users-admin/" class="btn"><i class="icon-remove"></i> Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "users_admin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Création d'utilisateur{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Créer un utilisateur <em>login</em><h1>
|
||||
<h3>ou un ensemble d'utilisateurs <em>login-N</em></h2>
|
||||
|
||||
<br />
|
||||
|
||||
<form action="" method="post" autocomplete="off">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="Créer" class="btn btn-primary" />
|
||||
<a href="." class="btn"><i class="icon-remove"></i> Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="{% static "bootstrap/js/bootstrap-datepicker.js" %}"></script>
|
||||
<script>
|
||||
$(function(){ $('input.datepicker').datepicker({ format: 'dd/mm/yyyy', weekStart: 1 }); });
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,46 @@
|
|||
{% extends "users_admin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Importer des utilisateus{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Importer des utilisateurs</h1>
|
||||
|
||||
<br />
|
||||
|
||||
<form action="" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
<h3>Format du fichier attendu : CSV</h3>
|
||||
<ul>
|
||||
<li>4 colonnes : login, nom complet, date d'expiration, mot de passe ;</li>
|
||||
<li>date au format AAAA-MM-DD ;</li>
|
||||
<li>si le mot de passe n'est pas fourni, un mot de passe sera généré ;</li>
|
||||
<li>si l'utilisateur existe déjà, il sera mis à jour ;</li>
|
||||
<li>séparateur : la virgule ;</li>
|
||||
<li>codage UTF-8 ;</li>
|
||||
<li>la première ligne ne sera pas prise en compte.</li>
|
||||
</ul>
|
||||
<h3>Exemple</h3>
|
||||
<pre class="span6">
|
||||
"login","nom complet","expiration","mot de passe"
|
||||
"joe","Joseph","2016-01-01","GxLGFP2"
|
||||
"bar","Anne Honyme","2010-01-01",""
|
||||
"team","","2014-07-22",""
|
||||
</pre>
|
||||
</div></div>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="Importer" class="btn btn-primary" />
|
||||
<a href="./" class="btn"><i class="icon-remove"></i> Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,108 @@
|
|||
{% extends "users_admin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Accueil{% endblock %}
|
||||
|
||||
{% block nav %}
|
||||
<li><a href="create"><i class="icon-plus"></i> Créer des utilisateurs</a></li>
|
||||
<li><a href="import"><i class="icon-download"></i> Importer des utilisateurs</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block title_content %}
|
||||
<div class="row">
|
||||
<div class="span8">
|
||||
<h1>Liste des utilisateurs</h1>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<form method="get" action="" class="form-inline pull-right">
|
||||
<div class="input-prepend input-append">
|
||||
<span class="add-on"><a href="./?filter="><i class="icon-remove-circle"></i></a></span>
|
||||
<input id="filter" type="text" name="filter" value="{{ filter }}" style="color: red; font-weight: bold;" placeholder="Filtre (avec * et ?)"/>
|
||||
<input type="submit" value="Filtrer" class="btn" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="multiple" method="post">
|
||||
|
||||
<br />
|
||||
<table class="table table-condensed table-striped" id="usersTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th data-sort="string">login</th>
|
||||
<th data-sort="string">actif?</th>
|
||||
<th data-sort="string">expiration</th>
|
||||
<th data-sort="string">nom complet</th>
|
||||
<th><input type="checkbox" id="check-all" /></th>
|
||||
<script>
|
||||
$('#check-all').click(function (event) {
|
||||
$('input[name="users"]').each(function () {
|
||||
this.click();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users.values|dictsort:"name" %}
|
||||
<tr {% if user.disabled or user.ttl == 0 %} class="disabled"{% endif %}>
|
||||
<td style="text-align: right;">
|
||||
<a href="update/{{ user.name }}"><i class="icon-edit"></i></a>
|
||||
</td>
|
||||
<td data-order-by="{{ user.name }}">
|
||||
<a href="read/{{ user.name }}" {% if user.disabled or user.ttl == 0 %} class="disabled"{% endif %}>{{ user.name }}</a>
|
||||
{% if 'univnautes-idp-multiple' in user.priv %} — multiple{% endif %}
|
||||
{% if user.disabled %} — désactivé{% endif %}
|
||||
{% if user.ttl == 0 %} — expiré{% endif %}
|
||||
</td>
|
||||
<td data-order-by="{% if user.disabled %}DIS{% elif user.ttl == 0 %}EXP{% else %}ACT{% endif %}">
|
||||
{% if user.disabled %}<i class="icon-pause"></i>
|
||||
{% elif user.ttl == 0 %}<i class="icon-stop"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td data-order-by="{{ user.expires|date:"c"|default:"2999-12-31" }}">
|
||||
{{ user.expires|default:"jamais" }}
|
||||
{% if user.ttl > 0 %} ({{ user.ttl }} jour{% if user.ttl > 1 %}s{% endif %})
|
||||
{% elif user.ttl == 0 %} — expiré{% endif %}
|
||||
</td>
|
||||
<td>{{ user.descr }}</td>
|
||||
<td><input type="checkbox" name="users" value="{{ user.name }}" /></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="form-actions">
|
||||
|
||||
<a href="create" class="btn"><i class="icon-plus"></i> Créer des utilisateurs</a>
|
||||
<a href="import" class="btn"><i class="icon-download"></i> Importer des utilisateurs</a>
|
||||
<div class=" pull-right">
|
||||
<input type="submit" value="Ok" class="btn pull-right" />
|
||||
<select name="action" class="pull-right">
|
||||
<option value="none" selected="selected">Choisir une action ...</option>
|
||||
<option value="desactivate">Désactiver les utilisateurs choisis</option>
|
||||
<option value="activate">Activer les utilisateurs choisis</option>
|
||||
<option value="delete">Supprimer les utilisateurs choisis</option>
|
||||
<option value="read">Afficher les utilisateurs choisis</option>
|
||||
<option value="csv">Exporter en CSV les utilisateurs choisis</option>
|
||||
<!-- option value="expire">Changer la date d'expiration des utilisateurs choisis</option -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
|
||||
<script src="{% static "jquery/js/stupidtable.js" %}"></script>
|
||||
<script>
|
||||
$(function(){ $("#usersTable").stupidtable(); });
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,71 @@
|
|||
{% load staticfiles %}<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Utilisateurs UnivNautes</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<link href="{% static "bootstrap/css/bootstrap.css" %}" rel="stylesheet">
|
||||
<style>
|
||||
body { padding-top: 60px; }
|
||||
th { text-align: right; padding-right: 20px; vertical-align: top; }
|
||||
.disabled { color: #ccc; }
|
||||
ul.errorlist { list-style-type: none; margin: 0; }
|
||||
ul.errorlist li { color: #f00; }
|
||||
.helptext { font-size: 0.8em; font-style: italic; }
|
||||
|
||||
div.user-block {
|
||||
position: relative;
|
||||
margin: 15px 0;
|
||||
padding: 0px 19px 14px;
|
||||
background-color: #fff;
|
||||
border: 2px solid #222;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
span.user-descr {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link href="{% static "bootstrap/css/bootstrap-responsive.css" %}" rel="stylesheet">
|
||||
|
||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="{% static "html5shiv/js/html5shiv.js" %}></script>
|
||||
<![endif]-->
|
||||
|
||||
<script type="text/javascript" src="{% static "jquery/js/jquery-1.10.2.min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.min.js" %}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<span class="brand">Utilisateurs IdP UnivNautes — {% now "j F Y H:i" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
{% for user in users %}
|
||||
<div class="user-block">
|
||||
<h2>
|
||||
Login : {{ user.name }}
|
||||
{% if user.descr %}<span class="user-descr"> — {{ user.descr }}</span>{% endif %}
|
||||
</h2>
|
||||
{% if user.password %}<h4>Mot de passe : {{ user.password }}</h4>{% endif %}
|
||||
<p>Expire le {{ user.expires }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,44 @@
|
|||
{% extends "users_admin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Utilisateur {{ user.name}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Utilisateur <{{ user.name }}></h1>
|
||||
|
||||
<span style="font-size: 1.2em;">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Login</dt> <dd>{{ user.name }}
|
||||
{% if user.disabled %}
|
||||
— <strong>désactivé</strong>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>Nom complet</dt> <dd>{{ user.descr }} </dd>
|
||||
<dt>Expiration</dt> <dd>{{ user.expires }} —
|
||||
{% if user.ttl %}{{user.ttl}} jour{% if user.ttl > 1 %}s{% endif %}{% else %}<strong>expiré</strong>{% endif %}</dd>
|
||||
{% if user.password %}
|
||||
<dt>Mot de passe</dt> <dd><span id="hidepass" style="display: none;">{{ user.password }}</span>
|
||||
<span id="showpass">(cliquer ici pour l'afficher)</span>
|
||||
<script>
|
||||
$('#showpass').click(function() {$('#hidepass').show(); $('#showpass').hide();});
|
||||
$('#hidepass').click(function() {$('#showpass').show(); $('#hidepass').hide();});
|
||||
</script>
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</span>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href=".." class="btn btn-primary"><i class="icon-list"></i> Retour à la liste</a>
|
||||
<a href="../update/{{ user.name }}" class="btn"><i class="icon-edit"></i> Modifier l'utilisateur</a>
|
||||
{% if user.disabled %}
|
||||
<a href="../activate/{{ user.name }}" class="btn"><i class="icon-play"></i> Activer l'utilisateur</a>
|
||||
{% else %}
|
||||
<a href="../desactivate/{{ user.name }}" class="btn"><i class="icon-pause"></i> Désactiver l'utilisateur</a>
|
||||
{% endif %}
|
||||
<a href="../delete/{{ user.name }}" class="btn btn-danger pull-right"><i class="icon-trash"></i> Supprimer l'utilisateur</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,36 @@
|
|||
{% extends "users_admin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Modifier un utilisateur{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Modifier l'utilisateur <{{ user.name }}></h1>
|
||||
|
||||
<br />
|
||||
|
||||
<form action="" method="post" autocomplete="off">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="Modifier l'utilisateur" class="btn btn-primary" />
|
||||
{% if user.disabled %}
|
||||
<a href="../activate/{{ user.name }}" class="btn"><i class="icon-play"></i> Activer l'utilisateur</a>
|
||||
{% else %}
|
||||
<a href="../desactivate/{{ user.name }}" class="btn"><i class="icon-pause"></i> Désactiver l'utilisateur</a>
|
||||
{% endif %}
|
||||
<a href=".." class="btn"><i class="icon-remove"></i> Annuler</a>
|
||||
<a href="../delete/{{ user.name }}" class="btn btn-danger pull-right"><i class="icon-trash"></i> Supprimer l'utilisateur</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="{% static "bootstrap/js/bootstrap-datepicker.js" %}"></script>
|
||||
<script>
|
||||
$(function(){ $('input.datepicker').datepicker({ format: 'dd/mm/yyyy', weekStart: 1 }); });
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,2 @@
|
|||
"login","nom complet","expiration","mot de passe"{% for user in users %}
|
||||
"{{ user.name|addslashes }}","{{ user.descr|safe|addslashes }} ","{{ user.expires|date:"c" }}","{{ user.password|default_if_none:"-"|safe|addslashes }}"{% endfor %}
|
Can't render this file because it contains an unexpected character in line 1 and column 49.
|
|
@ -0,0 +1,21 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.conf import settings
|
||||
import authentic2.idp.views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^', include('authentic2.auth2_auth.urls')),
|
||||
url(r'^logout$', 'authentic2.idp.views.logout', name='auth_logout'),
|
||||
url(r'^idp/', include('authentic2.idp.urls')),
|
||||
url(r'^users-admin/', include('idp.users_admin.urls')),
|
||||
url(r'^static/(?P<path>.*)$', 'idp.views.static_serve'),
|
||||
url(r'^$', login_required(authentic2.idp.views.homepage), {}, 'index'),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
urlpatterns += patterns('',
|
||||
(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
import datetime
|
||||
import pfusers
|
||||
|
||||
class ConfirmForm(forms.Form):
|
||||
pass
|
||||
|
||||
class UploadFileForm(forms.Form):
|
||||
file = forms.FileField(label=u"Fichier", required=True)
|
||||
|
||||
class UserForm(forms.Form):
|
||||
name = forms.RegexField(label=u"Nom d'utilisateur (login)", regex='^[a-z0-9\.\-_]+$', min_length=3, max_length=16, required=True,
|
||||
widget=forms.TextInput(attrs={'readonly': True, 'size':'16', 'autocomplete':'off', 'class':'span2'}))
|
||||
disabled = forms.BooleanField(label=u'Désactivé',required=False)
|
||||
password = forms.CharField(label=u"Nouveau mot de passe", min_length=3, max_length=64, required=False,
|
||||
widget=forms.PasswordInput(attrs={'size':'32', 'autocomplete':'off', 'class':'span3'}))
|
||||
password2 = forms.CharField(label=u"Mot de passe (vérification)", min_length=3, max_length=64, required=False,
|
||||
widget=forms.PasswordInput(attrs={'size':'32', 'autocomplete':'off', 'class':'span3'}))
|
||||
expires = forms.DateField(label=u"Date d'expiration", required=False,
|
||||
widget=forms.TextInput(attrs={'size':'16', 'class':'span2 datepicker'}))
|
||||
descr = forms.CharField(label=u'Description (nom long)', max_length=256, required=False,
|
||||
widget=forms.TextInput(attrs={'size':'256', 'class':'span6'}))
|
||||
multiple = forms.BooleanField(label=u'Connexions multiples autorisées',required=False)
|
||||
|
||||
def is_valid(self):
|
||||
valid = super(UserForm, self).is_valid()
|
||||
if valid:
|
||||
# check passwords
|
||||
password = self.cleaned_data.get('password')
|
||||
password2 = self.cleaned_data.get('password2')
|
||||
if password != password2:
|
||||
self.errors['password'] = [u'Les deux mots de passe doivent être identiques']
|
||||
valid = False
|
||||
return valid
|
||||
|
||||
class NewUserForm(UserForm):
|
||||
name = forms.RegexField(label=u"Nom d'utilisateur (login)", regex='^[a-z0-9\.\-_]+$', min_length=3, max_length=16, required=True,
|
||||
widget=forms.TextInput(attrs={'size':'16', 'autocomplete':'off', 'class':'span2'}),
|
||||
help_text="""Uniquement lettres, chiffres, point, trait d'union et «_».
|
||||
Si création de plusieurs utilisateurs (voir plus bas), leur login sera de la forme login-N.""")
|
||||
# password and expire fields
|
||||
password = forms.CharField(label=u"Mot de passe", min_length=3, max_length=64, required=False,
|
||||
help_text="""Si vous n'indiquez pas de mot de passe, un mot de passe aléatoire sera
|
||||
attribué à chaque utilisateur""",
|
||||
widget=forms.PasswordInput(attrs={'size':'32', 'autocomplete':'off', 'class':'span3'}))
|
||||
expires = forms.DateField(label=u"Date d'expiration", required=True,
|
||||
widget=forms.TextInput(attrs={'size':'16', 'class':'span2 datepicker'}))
|
||||
userset_number = forms.IntegerField(label=u"Nombre d'utilisateur(s) à créer (login-N)",
|
||||
widget=forms.TextInput(attrs={'size':'5', 'class':'span1'}),
|
||||
required=True, min_value=1)
|
||||
userset_start = forms.IntegerField(label=u"""En cas de création de plusieurs utilisateurs (login-N),
|
||||
indiquer le premier numéro N""",
|
||||
widget=forms.TextInput(attrs={'size':'5', 'class':'span1'}),
|
||||
required=True)
|
||||
|
||||
def is_valid(self):
|
||||
valid = super(NewUserForm, self).is_valid()
|
||||
if valid:
|
||||
name = self.cleaned_data.get('name')
|
||||
all_pfusers = pfusers.get_all_pfusers()
|
||||
userset_number = int(self.cleaned_data.get('userset_number'))
|
||||
if userset_number == 1 and all_pfusers.get(name, None) is not None:
|
||||
self.errors['name'] = [u'Un utilisateur avec ce login existe déjà.']
|
||||
return False
|
||||
if userset_number > 1:
|
||||
userset_start = int(self.cleaned_data.get('userset_start'))
|
||||
for n in range(userset_start, userset_start + userset_number):
|
||||
if all_pfusers.get('%s-%d' % (name, n), None) is not None:
|
||||
self.errors['name'] = [u"L'utilisateur %s-%d existe déjà." % (name, n)]
|
||||
return False
|
||||
return valid
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
#!/usr/local/bin/php -f
|
||||
<?php
|
||||
|
||||
/*
|
||||
* pf_useradm [action] [arg0=...] [arg1=....] ...
|
||||
* action: create | delete | modify
|
||||
* args: name, password, descr, expires, disabled, groups (comma separated), privs (comma separated)
|
||||
*
|
||||
* Note : a lot of this code is borrowed from pfsense /usr/local/www/system_usermanager.php
|
||||
*
|
||||
*/
|
||||
|
||||
$actions = array("create", "update", "delete");
|
||||
$keys = array("name", "password", "descr", "expires", "disabled", "groups", "privs");
|
||||
|
||||
array_shift($argv);
|
||||
|
||||
$action = $argv[0];
|
||||
if (!in_array($action, $actions)) {
|
||||
echo "BAD SYNTAX: incorrect action '" . $action . "'\n";
|
||||
exit(2);
|
||||
}
|
||||
|
||||
array_shift($argv);
|
||||
|
||||
$userargs=array();
|
||||
|
||||
foreach ($argv as $arg) {
|
||||
list($name, $value) = explode('=', $arg, 2);
|
||||
if (in_array($name, $keys)) {
|
||||
$userargs[$name] = $value;
|
||||
} else {
|
||||
echo "BAD SYNTAX: incorrect arg '" . $name . "'\n";
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check and normalize
|
||||
|
||||
if (!$userargs['name']) {
|
||||
echo "ERROR: missing name\n";
|
||||
exit(2);
|
||||
}
|
||||
|
||||
if (preg_match("/[^a-zA-Z0-9\.\-_]/", $userargs['name'])) {
|
||||
echo "ERROR: invalid characters in name\n";
|
||||
exit(2);
|
||||
}
|
||||
if (strlen($userargs['name']) > 16) {
|
||||
echo "ERROR: too long name (16 chars max)\n";
|
||||
exit(2);
|
||||
}
|
||||
|
||||
if ($userargs['expires']) {
|
||||
$expires = strtotime($userargs['expires']);
|
||||
if ($expires > 0) {
|
||||
$userargs['expires'] = date("m/d/Y",$expires);
|
||||
} else {
|
||||
echo "CANNOT CREATE: bad expiration '" . $userargs['expires'] . "'date (use mm/dd/yyyy)\n";
|
||||
exit(3);
|
||||
}
|
||||
} else
|
||||
unset($userargs['expires']);
|
||||
|
||||
if (preg_match("/^[Yy]/i", $userargs['disabled']))
|
||||
$userargs['disabled']= true;
|
||||
else
|
||||
unset($userargs['disabled']);
|
||||
|
||||
if ($userargs['privs'])
|
||||
$userargs['priv'] = explode(',', $userargs['privs']);
|
||||
elseif (isset($userargs['privs'])) // handle privs=""
|
||||
$userargs['priv'] = array();
|
||||
unset($userargs['privs']);
|
||||
|
||||
if ($userargs['groups'])
|
||||
$groups = explode(',', $userargs['groups']);
|
||||
elseif (isset($userargs['groups'])) // handle groups=""
|
||||
$groups = array();
|
||||
else
|
||||
unset($groups);
|
||||
unset($userargs['groups']);
|
||||
|
||||
|
||||
// now we need some functions and globals variables ($config)
|
||||
|
||||
require("auth.inc");
|
||||
|
||||
|
||||
// check that groups exist
|
||||
|
||||
if (isset($groups)) {
|
||||
$conf_groups = array();
|
||||
foreach ($config['system']['group'] as $gidx => $group) {
|
||||
$conf_groups[] = $group['name'];
|
||||
}
|
||||
foreach ($groups as $group)
|
||||
if (!in_array($group, $conf_groups)) {
|
||||
echo "ERROR: group '" . $group . "' does not exist\n";
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// search the user...
|
||||
|
||||
$user = getUserEntry($userargs["name"]);
|
||||
|
||||
|
||||
// handle the action
|
||||
|
||||
if ($action == "create") {
|
||||
if ($user) {
|
||||
echo "CANNOT CREATE: user '" . $userargs['name'] . "' already exists\n";
|
||||
exit(3);
|
||||
}
|
||||
// encrypt password
|
||||
if ($userargs['password'])
|
||||
local_user_set_password($userargs, $userargs['password']);
|
||||
else {
|
||||
echo "CANNOT CREATE: missing password\n";
|
||||
exit(3);
|
||||
}
|
||||
|
||||
// add last bits in the user
|
||||
$userargs['scope'] = 'user';
|
||||
$userargs['authorizedkeys'] = '';
|
||||
$userargs['ipsecpsk'] = '';
|
||||
|
||||
$system_users = explode("\n", file_get_contents("/etc/passwd"));
|
||||
foreach ($system_users as $s_user) {
|
||||
$ent = explode(":", $s_user);
|
||||
if ($ent[0] == $userargs['name']) {
|
||||
echo "ERROR: name '" . $userargs['name'] . "' is reserved by the system\n";
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
conf_mount_rw();
|
||||
|
||||
$new_user = $userargs;
|
||||
$new_user['uid'] = $config['system']['nextuid']++;
|
||||
|
||||
// Add the user to "All Users" group (borrowed from pfsense, don't known if its usefull)
|
||||
foreach ($config['system']['group'] as $gidx => $group) {
|
||||
if ($group['name'] == "all") {
|
||||
if (!is_array($config['system']['group'][$gidx]['member']))
|
||||
$config['system']['group'][$gidx]['member'] = array();
|
||||
$config['system']['group'][$gidx]['member'][] = $new_user['uid'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$all_users = &$config['system']['user'];
|
||||
$all_users[] = $new_user;
|
||||
|
||||
if (isset($groups))
|
||||
local_user_set_groups($new_user, $groups);
|
||||
local_user_set($new_user);
|
||||
|
||||
write_config();
|
||||
conf_mount_ro();
|
||||
echo "USER CREATED: " . $userargs['name'] . " \n";
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// delete ou update : search the user...
|
||||
if (!$user) {
|
||||
echo "CANNOT " . strtoupper($action) . ": user '" . $userargs['name'] . "' is unknown\n";
|
||||
exit(3);
|
||||
}
|
||||
|
||||
unset($id);
|
||||
$all_users = &$config['system']['user'];
|
||||
foreach ($all_users as $iter_id=>$iter_user) {
|
||||
if ($iter_user['name'] == $user['name']) {
|
||||
$id = $iter_id;
|
||||
}
|
||||
}
|
||||
if (!isset($id)) {
|
||||
echo "CANNOT " . strtoupper($action) . ": cannot find user '" . $userargs['name'] . "' (please report this error)\n";
|
||||
exit(3);
|
||||
}
|
||||
|
||||
if ($action == "update") {
|
||||
|
||||
// encrypt password
|
||||
if ($userargs['password'])
|
||||
local_user_set_password($userargs, $userargs['password']);
|
||||
else
|
||||
unset($userargs['password']);
|
||||
|
||||
conf_mount_rw();
|
||||
|
||||
$updated_user = $all_users[$id];
|
||||
// echo "BEFORE\n"; print_r($updated_user);
|
||||
foreach ($userargs as $key=>$value)
|
||||
$updated_user[$key] = $value;
|
||||
|
||||
if (!$userargs['disabled'])
|
||||
unset($updated_user['disabled']);
|
||||
// echo "AFTER\n"; print_r($updated_user);
|
||||
|
||||
$all_users[$id] = $updated_user;
|
||||
if (isset($groups))
|
||||
local_user_set_groups($updated_user, $groups);
|
||||
local_user_set($updated_user);
|
||||
write_config();
|
||||
conf_mount_ro();
|
||||
|
||||
echo "UPDATED USER: " . $userargs['name'] . "\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ($action == "delete") {
|
||||
local_user_del($all_users[$id]);
|
||||
unset($all_users[$id]);
|
||||
write_config();
|
||||
echo "DELETED USER: " . $userargs['name'] . "\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
exit(1);
|
||||
|
||||
?>
|
|
@ -0,0 +1,213 @@
|
|||
import xml.etree.ElementTree as ET
|
||||
try:
|
||||
from django.conf import settings
|
||||
PF_CONFIG_XML = settings.CONFIG_XML
|
||||
CLEAR_PASSWORD_DIR = settings.CLEAR_PASSWORD_DIR
|
||||
except ImportError:
|
||||
PF_CONFIG_XML = '/conf/config.xml'
|
||||
CLEAR_PASSWORD_DIR = '/var/db/univnautes/pfidp/passwords'
|
||||
|
||||
import datetime
|
||||
import subprocess
|
||||
import syslog
|
||||
|
||||
import htmlentitydefs
|
||||
import re
|
||||
import random
|
||||
import fnmatch
|
||||
import os
|
||||
|
||||
pattern = re.compile("&(\w+?);")
|
||||
|
||||
def html_entity_decode_char(m, defs=htmlentitydefs.entitydefs):
|
||||
try:
|
||||
return defs[m.group(1)]
|
||||
except KeyError:
|
||||
return m.group(0)
|
||||
|
||||
def html_entity_decode(string):
|
||||
return pattern.sub(html_entity_decode_char, string)
|
||||
|
||||
|
||||
def configxml():
|
||||
f = open(PF_CONFIG_XML,'r')
|
||||
root = ET.fromstring(f.read())
|
||||
f.close()
|
||||
return root
|
||||
|
||||
def create_password(username):
|
||||
if not os.path.exists(CLEAR_PASSWORD_DIR):
|
||||
os.makedirs(CLEAR_PASSWORD_DIR)
|
||||
password = ''.join([random.choice('23456789ABCDEFGHJLMNPQRSTUVWXZabcdefghjkmnpqrstuvwxyz')
|
||||
for x in range(random.randint(6,9))])
|
||||
filename = os.path.join(CLEAR_PASSWORD_DIR, 'user-%s' % username)
|
||||
f = open(filename, 'wb')
|
||||
f.write(password)
|
||||
f.close()
|
||||
return password
|
||||
|
||||
def read_password(username):
|
||||
filename = os.path.join(CLEAR_PASSWORD_DIR, 'user-%s' % username)
|
||||
try:
|
||||
f = open(filename, 'rb')
|
||||
password = f.read()
|
||||
f.close()
|
||||
return password
|
||||
except:
|
||||
return None
|
||||
|
||||
def delete_password(username):
|
||||
filename = os.path.join(CLEAR_PASSWORD_DIR, 'user-%s' % username)
|
||||
try:
|
||||
os.unlink(filename)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def get_all_pfusers(filter=None, with_password=False):
|
||||
xml_users = configxml().findall('system/user')
|
||||
if xml_users is None:
|
||||
return {}
|
||||
users = {}
|
||||
|
||||
for xml_user in xml_users:
|
||||
scope = xml_user.find('scope')
|
||||
if scope is None or scope.text != 'user':
|
||||
continue
|
||||
|
||||
user = dict([(tag, xml_user.findtext(tag))
|
||||
for tag in ('uid', 'name', 'expires', 'descr')])
|
||||
|
||||
if filter and not fnmatch.fnmatch(user['name'], filter+'*'):
|
||||
continue
|
||||
|
||||
user['descr'] = html_entity_decode(user['descr']).decode('iso-8859-1')
|
||||
|
||||
user['priv'] = set([priv.text for priv in xml_user.findall('priv')])
|
||||
|
||||
if with_password:
|
||||
user['password'] = read_password(user['name'])
|
||||
|
||||
expires = user.get('expires')
|
||||
if expires:
|
||||
try:
|
||||
user['expires'] = datetime.datetime.strptime(expires, '%m/%d/%Y').date() # pfSense format is mm/dd/YYYY
|
||||
user['ttl'] = (user['expires'] - datetime.date.today()).days
|
||||
if user['ttl'] < 0:
|
||||
user['ttl'] = 0
|
||||
except:
|
||||
# pfSense xml error ? ok, I suppose the account is expired
|
||||
user['expires'] = datetime.date.today()
|
||||
user['ttl'] = 0
|
||||
else:
|
||||
user['expires'] = None
|
||||
user['ttl'] = -1 # no expiration
|
||||
|
||||
user['disabled'] = xml_user.find('disabled') is not None
|
||||
|
||||
users[user['uid']] = user
|
||||
|
||||
# priv from groups
|
||||
xml_groups = configxml().findall('system/group')
|
||||
if not xml_groups:
|
||||
return users
|
||||
for xml_group in xml_groups:
|
||||
xml_members = xml_group.findall('member')
|
||||
if not xml_members:
|
||||
continue
|
||||
xml_privs = xml_group.findall('priv')
|
||||
if not xml_privs:
|
||||
continue
|
||||
privs = set([priv.text for priv in xml_privs])
|
||||
for uid in [xml_member.text for xml_member in xml_members]:
|
||||
user = users.get(uid)
|
||||
if user:
|
||||
user['priv'].update(privs)
|
||||
|
||||
# 1) keep only users with the "univnautes-idp" privilege
|
||||
# 1bis) and without "univnautes-idp-admin" privilege
|
||||
# 2) new index of the dict is the username (instead of the uid)
|
||||
users = dict([(users[uid]['name'], users[uid]) for uid in users
|
||||
if ('univnautes-idp' in users[uid]['priv']) and ('univnautes-idp-admin' not in users[uid]['priv'])])
|
||||
return users
|
||||
|
||||
def call(action, **kwargs):
|
||||
cmd = ['/usr/local/univnautes/idp/idp/users_admin/pf_useradm', ]
|
||||
cmd.append(action)
|
||||
cmd += [ '%s=%s' % (k,v.encode('iso-8859-1')) for k,v in kwargs.items() ]
|
||||
try:
|
||||
p = subprocess.Popen(cmd, close_fds=True,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
except OSError, e:
|
||||
syslog.openlog("idpusersadmin/pf_useradmin", syslog.LOG_PID)
|
||||
syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO,
|
||||
"ERROR %s: OSError %s" % (action, e))
|
||||
return False, "ERROR: OSError %s" % e
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
syslog.openlog("idpusersadmin/pf_useradmin", syslog.LOG_PID)
|
||||
syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO,
|
||||
"ERROR %s: code %s (out:%s) (err:%s)" % (action, p.returncode, stdout, stderr))
|
||||
return False, stdout + stderr
|
||||
syslog.openlog("idpusersadmin/pf_useradmin", syslog.LOG_PID)
|
||||
syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO,
|
||||
"SUCCESS %s: %s" % (action, stdout))
|
||||
return True, stdout
|
||||
|
||||
def create(username, password, expires, disabled=False, descr='', multiple=False):
|
||||
if isinstance(expires, datetime.date):
|
||||
expires_str = expires.strftime('%m/%d/%Y')
|
||||
else:
|
||||
expires_str = ''
|
||||
if disabled:
|
||||
disabled_str='yes'
|
||||
else:
|
||||
disabled_str='no'
|
||||
privs = 'univnautes-idp'
|
||||
if multiple:
|
||||
privs += ',univnautes-idp-multiple'
|
||||
if not password:
|
||||
password = create_password(username)
|
||||
return call('create', name=username, password=password,
|
||||
expires=expires_str,
|
||||
disabled=disabled_str,
|
||||
privs=privs,
|
||||
descr=descr)
|
||||
|
||||
def desactivate(username):
|
||||
return call('update', name=username, disabled='yes')
|
||||
|
||||
def activate(username):
|
||||
return call('update', name=username, disabled='no')
|
||||
|
||||
def delete(username):
|
||||
return call('delete', name=username)
|
||||
|
||||
def update(username, password, expires, disabled=False, descr='', multiple=False):
|
||||
if isinstance(expires, datetime.date):
|
||||
expires_str = expires.strftime('%m/%d/%Y')
|
||||
else:
|
||||
expires_str = ''
|
||||
if disabled:
|
||||
disabled_str='yes'
|
||||
else:
|
||||
disabled_str='no'
|
||||
privs = 'univnautes-idp'
|
||||
if multiple:
|
||||
privs += ',univnautes-idp-multiple'
|
||||
if password:
|
||||
delete_password(username)
|
||||
return call('update', name=username, password=password,
|
||||
expires=expires_str,
|
||||
disabled=disabled_str,
|
||||
privs=privs,
|
||||
descr=descr)
|
||||
else:
|
||||
return call('update', name=username,
|
||||
expires=expires_str,
|
||||
disabled=disabled_str,
|
||||
privs=privs,
|
||||
descr=descr)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
from django.conf.urls.defaults import patterns
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^$', login_required(views.index)),
|
||||
(r'^create$', login_required(views.create)),
|
||||
(r'^read/(?P<name>[a-z0-9\.\-_]+)$', login_required(views.read)),
|
||||
(r'^update/(?P<name>[a-z0-9\.\-_]+)$', login_required(views.update)),
|
||||
(r'^delete/(?P<name>[a-z0-9\.\-_]+)$', login_required(views.delete)),
|
||||
(r'^desactivate/(?P<name>[a-z0-9\.\-_]+)$', login_required(views.desactivate)),
|
||||
(r'^activate/(?P<name>[a-z0-9\.\-_]+)$', login_required(views.activate)),
|
||||
(r'^multiple$', login_required(views.multiple)),
|
||||
(r'^import$', login_required(views.csv_import)),
|
||||
)
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
# views for user admin
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
import csv
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render_to_response, redirect
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.template import RequestContext, loader, Context
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponse
|
||||
|
||||
import pfusers
|
||||
from .forms import UserForm, NewUserForm, ConfirmForm, UploadFileForm
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def index(request):
|
||||
filter = request.GET.get('filter', None)
|
||||
if filter is None:
|
||||
filter = request.COOKIES.get('filter', None)
|
||||
context = { 'users': pfusers.get_all_pfusers(filter),
|
||||
'filter': filter or '' }
|
||||
response = render_to_response('users_admin/index.html',
|
||||
context,
|
||||
context_instance=RequestContext(request))
|
||||
if filter is not None:
|
||||
response.set_cookie('filter', filter)
|
||||
return response
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def create(request):
|
||||
if request.method == 'POST':
|
||||
form = NewUserForm(request.POST)
|
||||
if form.is_valid():
|
||||
expires = form.cleaned_data.get('expires')
|
||||
name = form.cleaned_data.get('name')
|
||||
password = form.cleaned_data.get('password')
|
||||
descr = form.cleaned_data.get('descr')
|
||||
disabled = form.cleaned_data.get('disabled')
|
||||
multiple = form.cleaned_data.get('multiple')
|
||||
userset_number = int(form.cleaned_data.get('userset_number'))
|
||||
|
||||
if userset_number == 1:
|
||||
ret, log = pfusers.create(name, password=password, descr=descr,
|
||||
expires=expires, disabled=disabled, multiple=multiple)
|
||||
if ret:
|
||||
messages.success(request, u'Utilisateur <%s> ajouté.' % name)
|
||||
else:
|
||||
messages.error(request, u'Erreur lors de la création <%s>: %s' % (name, log))
|
||||
else: # (multiple users creation)
|
||||
userset_start = int(form.cleaned_data.get('userset_start'))
|
||||
for n in range(userset_start, userset_start+userset_number):
|
||||
username = '%s-%d' % (name, n)
|
||||
ret, log = pfusers.create(username, password=password, descr=descr,
|
||||
expires=expires, disabled=disabled, multiple=multiple)
|
||||
if not ret:
|
||||
messages.error(request, u'Erreur lors de la création <%s>: %s' % (username, log))
|
||||
break
|
||||
messages.success(request, u'Utilisateurs <%s-%d> à <%s-%d> ajoutés.' % \
|
||||
(name, userset_start, name, userset_start+userset_number-1))
|
||||
return redirect('.')
|
||||
else:
|
||||
initial = {
|
||||
'name': '',
|
||||
'userset_number': 1,
|
||||
'userset_start': 1,
|
||||
}
|
||||
dt = datetime.date.today() + datetime.timedelta(settings.IDP_UA_DEFAULT_EXPIRES)
|
||||
initial['expires'] = dt.strftime('%d/%m/%Y')
|
||||
form = NewUserForm(initial=initial)
|
||||
return render_to_response('users_admin/create.html',
|
||||
{ 'form': form, },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def read(request, name=None):
|
||||
user = pfusers.get_all_pfusers(with_password=True).get(name, None)
|
||||
if user == None:
|
||||
messages.error(request, u'Utilisateur <%s> inconnu.' % name)
|
||||
return redirect('..')
|
||||
return render_to_response('users_admin/read.html',
|
||||
{ 'user': user, },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def update(request, name=None):
|
||||
user = pfusers.get_all_pfusers().get(name, None)
|
||||
if user == None:
|
||||
messages.error(request, u'Utilisateur <%s> inconnu.' % name)
|
||||
return redirect('..')
|
||||
if request.method == 'POST':
|
||||
form = UserForm(request.POST)
|
||||
if form.is_valid():
|
||||
expires = form.cleaned_data.get('expires')
|
||||
password = form.cleaned_data.get('password')
|
||||
descr = form.cleaned_data.get('descr')
|
||||
disabled = form.cleaned_data.get('disabled')
|
||||
multiple = form.cleaned_data.get('multiple')
|
||||
ret, log = pfusers.update(name, password=password, descr=descr, expires=expires,
|
||||
disabled=disabled, multiple=multiple)
|
||||
if ret:
|
||||
messages.success(request, u'Utilisateur <%s> modifié.' % name)
|
||||
return redirect('..')
|
||||
else:
|
||||
messages.error(request, u'Erreur lors de la modification de <%s>: %s' % (name, log))
|
||||
return redirect('..')
|
||||
else:
|
||||
initial = {
|
||||
'name': user['name'],
|
||||
'descr': user['descr'],
|
||||
'multiple': 'univnautes-idp-multiple' in user['priv'],
|
||||
'disabled': user['disabled'],
|
||||
}
|
||||
if isinstance(user['expires'], datetime.date):
|
||||
initial['expires'] = user['expires'].strftime('%d/%m/%Y')
|
||||
form = UserForm(initial=initial)
|
||||
return render_to_response('users_admin/update.html',
|
||||
{ 'form': form, 'user': user, },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def delete(request, name=None):
|
||||
user = pfusers.get_all_pfusers().get(name, None)
|
||||
if user == None:
|
||||
messages.error(request, u'Utilisateur <%s> inconnu.' % name)
|
||||
return redirect('..')
|
||||
if request.method == 'POST':
|
||||
form = ConfirmForm(request.POST)
|
||||
if form.is_valid():
|
||||
ret, log = pfusers.delete(name)
|
||||
if ret:
|
||||
messages.success(request, u'Utilisateur <%s> supprimé.' % name)
|
||||
return redirect('..')
|
||||
else:
|
||||
messages.error(request, u'Erreur lors de la suppression de <%s>: %s' % (name, log))
|
||||
return redirect('..')
|
||||
form = ConfirmForm()
|
||||
return render_to_response('users_admin/confirm.html',
|
||||
{ 'form': form, 'users': [user] ,
|
||||
'title': u"Supprimer le compte <%s> ?" % name },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def desactivate(request, name=None):
|
||||
user = pfusers.get_all_pfusers().get(name, None)
|
||||
if user == None:
|
||||
messages.error(request, u'Utilisateur <%s> inconnu.' % name)
|
||||
return redirect('..')
|
||||
if request.method == 'POST':
|
||||
form = ConfirmForm(request.POST)
|
||||
if form.is_valid():
|
||||
ret, log = pfusers.desactivate(name)
|
||||
if ret:
|
||||
messages.success(request, u'Utilisateur <%s> désactivé.' % name)
|
||||
return redirect('..')
|
||||
else:
|
||||
messages.error(request, u'Erreur lors de la désactivation de <%s>: %s' % (name, log))
|
||||
return redirect('..')
|
||||
form = ConfirmForm()
|
||||
return render_to_response('users_admin/confirm.html',
|
||||
{ 'form': form, 'users': [user] ,
|
||||
'title': u"Désactiver le compte <%s> ?" % name },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def activate(request, name=None):
|
||||
user = pfusers.get_all_pfusers().get(name, None)
|
||||
if user == None:
|
||||
messages.error(request, u'Utilisateur <%s> inconnu.' % name)
|
||||
return redirect('..')
|
||||
if request.method == 'POST':
|
||||
form = ConfirmForm(request.POST)
|
||||
if form.is_valid():
|
||||
ret, log = pfusers.activate(name)
|
||||
if ret:
|
||||
messages.success(request, u'Utilisateur <%s> activé.' % name)
|
||||
return redirect('..')
|
||||
else:
|
||||
messages.error(request, u"Erreur lors de l'activation de <%s>: %s" % (name, log))
|
||||
return redirect('..')
|
||||
form = ConfirmForm()
|
||||
return render_to_response('users_admin/confirm.html',
|
||||
{ 'form': form, 'users': [user] ,
|
||||
'title': u"Activer le compte <%s> ?" % name },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
ACTION_NAME = {
|
||||
'delete': u'Suppression',
|
||||
'desactivate': u'Désactivation',
|
||||
'activate': u'Activation',
|
||||
'read': u'Afficher les utilisateurs',
|
||||
'csv': u'Export CSV',
|
||||
}
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def multiple(request):
|
||||
if request.method == 'POST':
|
||||
action = request.POST.get('action')
|
||||
if action:
|
||||
# we need a confirmation
|
||||
if not action in ACTION_NAME:
|
||||
messages.warning(request, u'Choisissez une action...')
|
||||
return redirect('.')
|
||||
else:
|
||||
title = '%s de ces comptes ?' % ACTION_NAME[action]
|
||||
names = request.POST.getlist('users')
|
||||
if len(names) == 0:
|
||||
messages.warning(request, u'Sélectionnez au moins un utilisateur.')
|
||||
return redirect('.')
|
||||
all_pfusers = pfusers.get_all_pfusers(with_password=(action in ['read','csv']))
|
||||
try:
|
||||
users = [ all_pfusers[name] for name in names ]
|
||||
except KeyError:
|
||||
messages.error(request, u'Au moins un utilisateur inconnu dans la liste.')
|
||||
return redirect('.')
|
||||
if action == "read":
|
||||
return render_to_response('users_admin/read-list.html',
|
||||
{ 'users': users, }, context_instance=RequestContext(request))
|
||||
if action == "csv":
|
||||
return csv_export(users)
|
||||
request.session['univnautes_idpua_action'] = action
|
||||
request.session['univnautes_idpua_names'] = names
|
||||
form = ConfirmForm()
|
||||
return render_to_response('users_admin/confirm.html',
|
||||
{ 'form': form,
|
||||
'users': users,
|
||||
'title': title },
|
||||
context_instance=RequestContext(request))
|
||||
else:
|
||||
# normally, it's a confirmation
|
||||
form = ConfirmForm(request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
names = request.session['univnautes_idpua_names']
|
||||
action = request.session['univnautes_idpua_action']
|
||||
except KeyError:
|
||||
messages.error(request, u'Erreur dans la session !')
|
||||
return redirect('.')
|
||||
if not action in ACTION_NAME:
|
||||
messages.error(request, u'Action invalide')
|
||||
return redirect('.')
|
||||
success = []
|
||||
errors = []
|
||||
for name in names:
|
||||
ret, log = getattr(pfusers, action)(name)
|
||||
if ret:
|
||||
success.append(name)
|
||||
else:
|
||||
errors.append('%s (%s)' % (name, log))
|
||||
if success:
|
||||
messages.success(request, u'%s: %s' % (ACTION_NAME[action], ', '.join(success)))
|
||||
if errors:
|
||||
messages.error(request, u'ERREUR %s: %s' % (ACTION_NAME[action], ', '.join(errors)))
|
||||
del request.session['univnautes_idpua_names']
|
||||
del request.session['univnautes_idpua_action']
|
||||
else:
|
||||
messages.error('Erreur lors de la confirmation.')
|
||||
|
||||
return redirect('.')
|
||||
|
||||
def csv_export(users):
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="users.csv"'
|
||||
t = loader.get_template('users_admin/users.csv')
|
||||
c = Context({ 'users': users, })
|
||||
response.write(t.render(c))
|
||||
return response
|
||||
|
||||
@user_passes_test(lambda user: user.is_staff, login_url='/logout')
|
||||
def csv_import(request):
|
||||
if request.method == 'POST':
|
||||
form = UploadFileForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
try:
|
||||
created_users, updated_users = import_csv_file(request.FILES['file'])
|
||||
except Exception as e:
|
||||
messages.error(request, u'Import du fichier impossible, erreur : %s' % e)
|
||||
else:
|
||||
messages.success(request, u'%d utilisateurs créés, %d mis à jour' % \
|
||||
(created_users, updated_users))
|
||||
return redirect('.')
|
||||
else:
|
||||
form = UploadFileForm()
|
||||
return render_to_response('users_admin/import.html',
|
||||
{'form': form},
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def import_csv_file(f):
|
||||
filename = '/var/tmp/users-import.csv'
|
||||
with open(filename, 'wb+') as destination:
|
||||
for chunk in f.chunks():
|
||||
destination.write(chunk)
|
||||
csvfile = open(filename, 'rb')
|
||||
dialect = csv.Sniffer().sniff(csvfile.read(1024))
|
||||
csvfile.seek(0)
|
||||
reader = csv.reader(csvfile, dialect)
|
||||
created_users = []
|
||||
updated_users = []
|
||||
all_pfusers = pfusers.get_all_pfusers()
|
||||
|
||||
# analyse all the file, then create (if no exception)
|
||||
|
||||
headers = reader.next()
|
||||
for row in reader:
|
||||
user = {
|
||||
'username': row[0].strip(),
|
||||
'descr': row[1].strip(),
|
||||
'expires': datetime.datetime.strptime(row[2],'%Y-%m-%d').date(),
|
||||
'password': row[3].strip() or None
|
||||
}
|
||||
if user['username'] in all_pfusers:
|
||||
updated_users.append(user)
|
||||
else:
|
||||
created_users.append(user)
|
||||
csvfile.close()
|
||||
|
||||
for user in created_users:
|
||||
pfusers.create(**user)
|
||||
for user in updated_users:
|
||||
pfusers.update(**user)
|
||||
|
||||
return len(created_users), len(updated_users)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# UnivNautes
|
||||
# Copyright (C) 2014 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from django.conf import settings
|
||||
from django.views.static import serve as django_static_serve
|
||||
|
||||
|
||||
def static_serve(request, path):
|
||||
# if path is "this/file.css", search for "captiveportal-idp-static-this-file.css"
|
||||
custom_path = 'captiveportal-idp-static-%s' % path.replace('/', '-')
|
||||
document_root = settings.CPELEMENTS
|
||||
if os.path.exists(os.path.join(document_root, custom_path)):
|
||||
return django_static_serve(request, custom_path, document_root=document_root)
|
||||
return django_static_serve(request, path, document_root=settings.STATIC_ROOT)
|
|
@ -0,0 +1,32 @@
|
|||
"""
|
||||
WSGI config for idp project.
|
||||
|
||||
This module contains the WSGI application used by Django's development server
|
||||
and any production WSGI deployments. It should expose a module-level variable
|
||||
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
||||
this application via the ``WSGI_APPLICATION`` setting.
|
||||
|
||||
Usually you will have the standard Django WSGI application here, but it also
|
||||
might make sense to replace the whole Django WSGI application with a custom one
|
||||
that later delegates to the Django one. For example, you could introduce WSGI
|
||||
middleware here, or combine a Django application with an application of another
|
||||
framework.
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
||||
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
||||
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
||||
# os.environ["DJANGO_SETTINGS_MODULE"] = "idp.settings"
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "idp.settings")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
||||
|
||||
# Apply WSGI middleware here.
|
||||
# from helloworld.wsgi import HelloWorldApplication
|
||||
# application = HelloWorldApplication(application)
|
|
@ -0,0 +1,169 @@
|
|||
#
|
||||
# lighttpd configuration file
|
||||
#
|
||||
# use a it as base for lighttpd 1.0.0 and above
|
||||
#
|
||||
############ Options you really have to take care of ####################
|
||||
|
||||
## FreeBSD!
|
||||
server.event-handler = "freebsd-kqueue"
|
||||
server.network-backend = "writev"
|
||||
#server.use-ipv6 = "enable"
|
||||
|
||||
## modules to load
|
||||
server.modules = ( "mod_access", "mod_expire", "mod_compress", "mod_redirect",
|
||||
,"mod_rewrite","mod_evasive", "mod_fastcgi", "mod_setenv",
|
||||
)
|
||||
|
||||
server.max-keep-alive-requests = 15
|
||||
server.max-keep-alive-idle = 30
|
||||
|
||||
## a static document-root, for virtual-hosting take look at the
|
||||
## server.virtual-* options
|
||||
server.document-root = "/usr/local/univnautes/idp/www"
|
||||
|
||||
|
||||
# Maximum idle time with nothing being written (php downloading)
|
||||
server.max-write-idle = 999
|
||||
|
||||
## where to send error-messages to
|
||||
server.errorlog-use-syslog="enable"
|
||||
|
||||
# files to check for if .../ is requested
|
||||
server.indexfiles = ( "index.html" )
|
||||
|
||||
# mimetype mapping
|
||||
mimetype.assign = (
|
||||
".pdf" => "application/pdf",
|
||||
".sig" => "application/pgp-signature",
|
||||
".spl" => "application/futuresplash",
|
||||
".class" => "application/octet-stream",
|
||||
".ps" => "application/postscript",
|
||||
".torrent" => "application/x-bittorrent",
|
||||
".dvi" => "application/x-dvi",
|
||||
".gz" => "application/x-gzip",
|
||||
".pac" => "application/x-ns-proxy-autoconfig",
|
||||
".swf" => "application/x-shockwave-flash",
|
||||
".tar.gz" => "application/x-tgz",
|
||||
".tgz" => "application/x-tgz",
|
||||
".tar" => "application/x-tar",
|
||||
".zip" => "application/zip",
|
||||
".mp3" => "audio/mpeg",
|
||||
".m3u" => "audio/x-mpegurl",
|
||||
".wma" => "audio/x-ms-wma",
|
||||
".wax" => "audio/x-ms-wax",
|
||||
".ogg" => "audio/x-wav",
|
||||
".wav" => "audio/x-wav",
|
||||
".gif" => "image/gif",
|
||||
".jpg" => "image/jpeg",
|
||||
".jpeg" => "image/jpeg",
|
||||
".png" => "image/png",
|
||||
".xbm" => "image/x-xbitmap",
|
||||
".xpm" => "image/x-xpixmap",
|
||||
".xwd" => "image/x-xwindowdump",
|
||||
".css" => "text/css",
|
||||
".html" => "text/html",
|
||||
".htm" => "text/html",
|
||||
".js" => "text/javascript",
|
||||
".asc" => "text/plain",
|
||||
".c" => "text/plain",
|
||||
".conf" => "text/plain",
|
||||
".text" => "text/plain",
|
||||
".txt" => "text/plain",
|
||||
".dtd" => "text/xml",
|
||||
".xml" => "text/xml",
|
||||
".mpeg" => "video/mpeg",
|
||||
".mpg" => "video/mpeg",
|
||||
".mov" => "video/quicktime",
|
||||
".qt" => "video/quicktime",
|
||||
".avi" => "video/x-msvideo",
|
||||
".asf" => "video/x-ms-asf",
|
||||
".asx" => "video/x-ms-asf",
|
||||
".wmv" => "video/x-ms-wmv",
|
||||
".bz2" => "application/x-bzip",
|
||||
".tbz" => "application/x-bzip-compressed-tar",
|
||||
".tar.bz2" => "application/x-bzip-compressed-tar"
|
||||
)
|
||||
|
||||
# Use the "Content-Type" extended attribute to obtain mime type if possible
|
||||
#mimetypes.use-xattr = "enable"
|
||||
|
||||
## deny access the file-extensions
|
||||
#
|
||||
# ~ is for backupfiles from vi, emacs, joe, ...
|
||||
# .inc is often used for code includes which should in general not be part
|
||||
# of the document-root
|
||||
url.access-deny = ( "~", ".inc" )
|
||||
|
||||
|
||||
######### Options that are good to be but not neccesary to be changed #######
|
||||
|
||||
## bind to port (default: 80)
|
||||
server.bind = "0.0.0.0"
|
||||
server.port = 4443
|
||||
$SERVER["socket"] == "0.0.0.0:4443" { }
|
||||
$SERVER["socket"] == "[::]:4443" {
|
||||
|
||||
## ssl configuration
|
||||
ssl.engine = "enable"
|
||||
ssl.pemfile = "/var/etc/cert-univnautes-portal.pem"
|
||||
ssl.ca-file = "/var/etc/ca-univnautes-portal.pem"
|
||||
}
|
||||
|
||||
## error-handler for status 404
|
||||
#server.error-handler-404 = "/error-handler.html"
|
||||
#server.error-handler-404 = "/error-handler.php"
|
||||
|
||||
## to help the rc.scripts
|
||||
server.pid-file = "/var/run/lighty-idp.pid"
|
||||
|
||||
## virtual directory listings
|
||||
server.dir-listing = "disable"
|
||||
|
||||
## enable debugging
|
||||
debug.log-request-header = "disable"
|
||||
debug.log-response-header = "disable"
|
||||
debug.log-request-handling = "disable"
|
||||
debug.log-file-not-found = "disable"
|
||||
|
||||
# gzip compression
|
||||
compress.cache-dir = "/tmp/lighttpdcompress/"
|
||||
compress.filetype = ("text/plain","text/css", "text/xml", "text/javascript" )
|
||||
|
||||
server.max-request-size = 384
|
||||
|
||||
#### fastcgi module
|
||||
fastcgi.server = (
|
||||
"/django.fcgi" => (
|
||||
"main" => (
|
||||
"socket" => "/tmp/univnautes-idp-fcgi.sock",
|
||||
"check-local" => "disable",
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
url.rewrite-if-not-file = (
|
||||
"^/map/(.*)$" => "/django.fcgi/proxymap/$1",
|
||||
)
|
||||
|
||||
url.rewrite-once = (
|
||||
"^/favicon\.ico$" => "/static/favicon.ico",
|
||||
"^/*$" => "/django.fcgi/",
|
||||
"^/(.*)$" => "/django.fcgi/$1",
|
||||
)
|
||||
|
||||
evasive.max-conns-per-ip = 64
|
||||
|
||||
expire.url = (
|
||||
"" => "access 50 hours",
|
||||
)
|
||||
|
||||
## ssl configuration
|
||||
ssl.engine = "enable"
|
||||
ssl.pemfile = "/var/etc/cert-univnautes-portal.pem"
|
||||
|
||||
ssl.use-sslv2 = "disable"
|
||||
ssl.use-sslv3 = "disable"
|
||||
ssl.cipher-list = "DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:CAMELLIA256-SHA:DHE-DSS-AES256-SHA:AES256-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:CAMELLIA128-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:AES128-SHA:RC4-SHA:RC4-MD5:!aNULL:!eNULL:!3DES:@STRENGTH"
|
||||
ssl.ca-file = "/var/etc/ca-univnautes-portal.pem"
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "idp.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
|
@ -0,0 +1,89 @@
|
|||
#!/usr/local/bin/bash
|
||||
|
||||
DB=/var/db/univnautes-idp.sqlite3
|
||||
IDPDIR=/usr/local/univnautes/idp
|
||||
|
||||
cd $IDPDIR
|
||||
|
||||
function cronstop() {
|
||||
for cron in idp-update-metadatas
|
||||
do
|
||||
if [ -r /var/run/${cron}-cron.pid ]
|
||||
then
|
||||
PID=`cat /var/run/${cron}-cron.pid`
|
||||
ps waux | grep "$PID" | grep minicron | grep -vq grep && kill $PID
|
||||
rm -f /var/run/${cron}-cron.pid
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function cronstart() {
|
||||
cronstop
|
||||
/usr/local/bin/minicron 3600 /var/run/idp-update-metadatas-cron.pid $IDPDIR/idp-update-metadatas.sh
|
||||
}
|
||||
|
||||
function lightystop() {
|
||||
if [ -r /var/run/lighty-idp.pid ]
|
||||
then
|
||||
PID=`cat /var/run/lighty-idp.pid`
|
||||
ps waux | grep "$PID" | grep lighty-idp | grep -vq grep && kill $PID
|
||||
rm -f /var/run/lighty-idp.pid
|
||||
fi
|
||||
}
|
||||
|
||||
function lightystart() {
|
||||
lightystop
|
||||
/usr/local/sbin/lighttpd -f /usr/local/univnautes/idp/lighty-idp.conf
|
||||
}
|
||||
|
||||
|
||||
function syncdata() {
|
||||
echo "sync metadatas in progress (backgrounded)" | logger -p local4.info -t idp/syncdata
|
||||
(
|
||||
cd /usr/local/univnautes/idp/
|
||||
./idp-update-metadatas.sh | logger -p local4.info -t idp/idp-update-metadatas
|
||||
) &
|
||||
}
|
||||
|
||||
function syncdb() {
|
||||
if ! test -r $DB
|
||||
then
|
||||
python manage.py syncdb --noinput --no-initial-data | logger -p local4.info -t idp/syncdb
|
||||
python manage.py loaddata fixtures/* | logger -p local4.info -t idp/loaddata
|
||||
fi
|
||||
}
|
||||
|
||||
function start() {
|
||||
if python manage.py configxml get idp > /dev/null
|
||||
then
|
||||
syncdb
|
||||
python manage.py collectstatic -v0 -l --noinput | logger -p local4.info -t idp/collectstatic
|
||||
python manage.py runfcgi socket=/tmp/univnautes-idp-fcgi.sock method=prefork daemonize=true pidfile=/var/run/univnautes-idp-fcgi.pid
|
||||
echo "started (manage.py runfcgi)" | logger -p local4.info -t idp/start
|
||||
syncdata
|
||||
cronstart
|
||||
lightystart
|
||||
fi
|
||||
}
|
||||
|
||||
function stop() {
|
||||
lightystop
|
||||
cronstop
|
||||
kill $(cat /var/run/univnautes-idp-fcgi.pid)
|
||||
echo "stopped (kill)" | logger -p local4.info -t idp/stop
|
||||
}
|
||||
|
||||
function restart() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
function status() {
|
||||
ps waux | grep $(cat /var/run/univnautes-idp-fcgi.pid) | grep -v grep
|
||||
}
|
||||
|
||||
echo $1 | logger -p local4.info -t idp/rc
|
||||
|
||||
$1
|
||||
|
Reference in New Issue