0001-backends-implement-a-new-ldap-storage-backend.patch
ldap/mandaye.schema | ||
---|---|---|
1 |
# vim:et:sw=4 |
|
2 |
# |
|
3 |
# CUD Schema |
|
4 |
# |
|
5 |
# Date : 20140908 |
|
6 |
# Révision : |
|
7 |
# * 20140908 - Jérôme Schneider <jschneider@entrouvert.com> - Création initiale |
|
8 |
# |
|
9 | ||
10 |
objectIdentifier EoRoot 1.3.6.1.4.1.36560 |
|
11 |
objectIdentifier EoClientRoot EoRoot:3 |
|
12 |
objectIdentifier MandayeRoot EoClientRoot:1127 |
|
13 |
objectIdentifier MandayeLdap MandayeRoot:1 |
|
14 |
objectIdentifier MandayeLdapAttributes MandayeLdap:1 |
|
15 |
objectIdentifier MandayeLdapObjectClass MandayeLdap:2 |
|
16 | ||
17 |
objectIdentifier Int 1.3.6.1.4.1.1466.115.121.1.27 |
|
18 |
objectIdentifier UTF8 1.3.6.1.4.1.1466.115.121.1.15 |
|
19 |
objectIdentifier Boolean 1.3.6.1.4.1.1466.115.121.1.7 |
|
20 |
objectIdentifier Binary 1.3.6.1.4.1.1466.115.121.1.40 |
|
21 |
objectIdentifier DateTime 1.3.6.1.4.1.1466.115.121.1.24 |
|
22 |
objectIdentifier IA5String 1.3.6.1.4.1.1466.115.121.1.26 |
|
23 | ||
24 |
attributetype (MandayeLdapAttributes:1 |
|
25 |
NAME 'uniqueID' |
|
26 |
DESC 'Mandaye user unique identifier' |
|
27 |
EQUALITY caseIgnoreMatch |
|
28 |
SYNTAX UTF8 |
|
29 |
SINGLE-VALUE ) |
|
30 | ||
31 |
attributetype (MandayeLdapAttributes:2 |
|
32 |
NAME 'idpUniqueID' |
|
33 |
DESC 'IDP unique id (like NameID)' |
|
34 |
EQUALITY caseIgnoreMatch |
|
35 |
SUBSTR caseIgnoreSubstringsMatch |
|
36 |
SYNTAX UTF8 |
|
37 |
SINGLE-VALUE ) |
|
38 | ||
39 |
attributetype (MandayeLdapAttributes:3 |
|
40 |
NAME 'idpName' |
|
41 |
DESC 'Name of the idp' |
|
42 |
EQUALITY caseIgnoreMatch |
|
43 |
SYNTAX UTF8 |
|
44 |
SINGLE-VALUE ) |
|
45 | ||
46 |
attributetype (MandayeLdapAttributes:4 |
|
47 |
NAME 'spLogin' |
|
48 |
DESC 'SP login' |
|
49 |
EQUALITY caseIgnoreMatch |
|
50 |
SYNTAX UTF8 |
|
51 |
SINGLE-VALUE ) |
|
52 | ||
53 |
attributetype (MandayeLdapAttributes:5 |
|
54 |
NAME 'spPostValues' |
|
55 |
DESC 'SP post values' |
|
56 |
EQUALITY caseIgnoreMatch |
|
57 |
SYNTAX UTF8 |
|
58 |
SINGLE-VALUE ) |
|
59 | ||
60 |
attributetype (MandayeLdapAttributes:6 |
|
61 |
NAME 'spName' |
|
62 |
DESC 'name of the service provider' |
|
63 |
EQUALITY caseIgnoreMatch |
|
64 |
SYNTAX UTF8 |
|
65 |
SINGLE-VALUE ) |
|
66 | ||
67 |
attributetype (MandayeLdapAttributes:7 |
|
68 |
NAME 'creationDate' |
|
69 |
DESC 'creation date of the association (ISO8601 format)' |
|
70 |
EQUALITY generalizedTimeMatch |
|
71 |
SYNTAX DateTime |
|
72 |
SINGLE-VALUE ) |
|
73 | ||
74 |
attributetype (MandayeLdapAttributes:8 |
|
75 |
NAME 'lastConnection' |
|
76 |
DESC 'Last connection date ISO8601' |
|
77 |
EQUALITY generalizedTimeMatch |
|
78 |
ORDERING generalizedTimeOrderingMatch |
|
79 |
SYNTAX DateTime |
|
80 |
SINGLE-VALUE ) |
|
81 | ||
82 |
# |
|
83 |
# Classes d'objets: |
|
84 |
# |
|
85 | ||
86 |
objectclass (MandayeLdapObjectClass:1 |
|
87 |
NAME 'MandayeUser' |
|
88 |
DESC 'Mandaye user Objectclass' |
|
89 |
SUP top |
|
90 |
STRUCTURAL |
|
91 |
MUST ( uniqueID $ idpUniqueId $ idpName $ spLogin $ spPostValues $ creationDate $ spName ) |
|
92 |
MAY ( lastConnection ) ) |
|
93 |
mandaye/backends/default.py | ||
---|---|---|
28 | 28 |
bind=create_engine(config.db_url) |
29 | 29 |
) |
30 | 30 |
) |
31 |
elif config.storage_backend == "mandaye.backends.ldap_back": |
|
32 |
import ldap |
|
33 |
storage_conn = ldap.initialize(config.ldap_url) |
|
34 |
storage_conn.protocol_version = ldap.VERSION3 |
|
35 |
storage_conn.simple_bind(config.ldap_bind_dn, config.ldap_bind_password) |
|
31 | 36 | |
32 | 37 |
backend = import_backend(config.storage_backend) |
33 | 38 |
Association = backend.Association |
mandaye/backends/ldap_back.py | ||
---|---|---|
1 | ||
2 |
import datetime |
|
3 |
import ldap |
|
4 |
import ldap.modlist |
|
5 |
import random |
|
6 | ||
7 |
from mandaye import config |
|
8 |
from mandaye.log import logger |
|
9 |
from mandaye.backends.default import storage_conn |
|
10 | ||
11 |
class Association(object): |
|
12 |
""" |
|
13 |
association dictionnary return by the following methods: |
|
14 |
{ |
|
15 |
'id': '', # identifier of your association (must be unique) |
|
16 |
'sp_name': '', # name of the service provider (defined in the mappers) |
|
17 |
'sp_login': '', # login on the service provider |
|
18 |
'sp_post_values': '', # the post values for sp login form |
|
19 |
'idp_unique_id:': '', # the unique identifier of the identity provider (ex.: a saml NameID) |
|
20 |
'idp_name': '', # identity provide name |
|
21 |
'last_connection': datetime.datetime, # last connection with this association |
|
22 |
'creation_date': datetime.datetime, # creation date of this association |
|
23 |
} |
|
24 |
""" |
|
25 | ||
26 |
@staticmethod |
|
27 |
def ldap2association(ldap_object): |
|
28 |
return { |
|
29 |
'id': ldap_object['uniqueID'][0], |
|
30 |
'sp_name': ldap_object['spName'][0], |
|
31 |
'sp_login': ldap_object['spLogin'][0], |
|
32 |
'sp_post_values': ldap_object['spPostValues'][0], |
|
33 |
'idp_unique_id': ldap_object['idpUniqueID'][0], |
|
34 |
'idp_name': ldap_object['idpName'][0], |
|
35 |
'last_connection': datetime.datetime.strptime( |
|
36 |
ldap_object['lastConnection'][0][:14], |
|
37 |
'%Y%m%d%H%M%S'), |
|
38 |
'creation_date': datetime.datetime.strptime( |
|
39 |
ldap_object['creationDate'][0][:14], |
|
40 |
'%Y%m%d%H%M%S'), |
|
41 |
} |
|
42 | ||
43 |
@staticmethod |
|
44 |
def get(sp_name, idp_unique_id, idp_name='default'): |
|
45 |
""" return a list of dict with associations that matching all of this options """ |
|
46 |
associations = [] |
|
47 |
results = storage_conn.search_s(config.ldap_base_dn, ldap.SCOPE_ONELEVEL, |
|
48 |
filterstr='(&(objectClass=MandayeUser)(spName=%s)(idpUniqueID=%s)(idpName=%s))' % (sp_name, idp_unique_id, idp_name)) |
|
49 |
for result in results: |
|
50 |
associations.append(Association.ldap2association(result[1])) |
|
51 |
return associations |
|
52 | ||
53 | ||
54 |
@staticmethod |
|
55 |
def get_by_id(asso_id): |
|
56 |
""" return an dict of the association with the id or None if it doesn't exist """ |
|
57 |
results = storage_conn.search_s(config.ldap_base_dn, ldap.SCOPE_ONELEVEL, |
|
58 |
filterstr='(&(objectClass=MandayeUser)(uniqueID=%s))' %\ |
|
59 |
(asso_id)) |
|
60 |
if results: |
|
61 |
return Association.ldap2association(results[0][1]) |
|
62 |
return None |
|
63 | ||
64 |
@staticmethod |
|
65 |
def has_id(asso_id): |
|
66 |
""" return a boolean """ |
|
67 |
results = storage_conn.search_s(config.ldap_base_dn, ldap.SCOPE_ONELEVEL, |
|
68 |
filterstr='(&(objectClass=MandayeUser)(uniqueID=%s))' %\ |
|
69 |
(asso_id)) |
|
70 |
if results: |
|
71 |
return True |
|
72 |
return False |
|
73 | ||
74 |
@staticmethod |
|
75 |
def update_or_create(sp_name, sp_login, sp_post_values, idp_unique_id, idp_name='default'): |
|
76 |
""" update or create an associtaion which match the following values |
|
77 |
return the association id |
|
78 | ||
79 |
""" |
|
80 |
results = storage_conn.search_s(config.ldap_base_dn, ldap.SCOPE_ONELEVEL, |
|
81 |
filterstr='(&(objectClass=MandayeUser)(spName=%s)(spLogin=%s)(idpUniqueID=%s)(idpName=%s))' %\ |
|
82 |
(sp_name, sp_login, idp_unique_id, idp_name)) |
|
83 |
if not results: |
|
84 |
association = {'spName': sp_name, |
|
85 |
'spLogin': sp_login, |
|
86 |
'spPostValues': sp_post_values, |
|
87 |
'idpUniqueID': idp_unique_id, |
|
88 |
'idpName': idp_name, |
|
89 |
'creationDate': datetime.datetime.utcnow().strftime('%Y%m%d%H%M%SZ'), |
|
90 |
'lastConnection': datetime.datetime.utcnow().strftime('%Y%m%d%H%M%SZ'), |
|
91 |
'objectClass': 'MandayeUser' |
|
92 |
} |
|
93 |
mod_list = ldap.modlist.addModlist(association) |
|
94 |
while True: |
|
95 |
unique_id = random.randint(1, 5000000) |
|
96 |
dn = "uniqueID=%s,%s" % (unique_id, config.ldap_base_dn) |
|
97 |
try: |
|
98 |
result = storage_conn.add_s(dn, mod_list) |
|
99 |
except ldap.ALREADY_EXISTS: |
|
100 |
continue |
|
101 |
break |
|
102 |
logger.info("New association %r with %r", sp_login, idp_unique_id) |
|
103 |
return unique_id |
|
104 |
else: |
|
105 |
dn = results[0][0] |
|
106 |
mod_list = [(ldap.MOD_REPLACE, 'spPostValues', sp_post_values)] |
|
107 |
storage_conn.modify_s(dn, mod_list) |
|
108 |
logger.info("Update post values for %r (%r)", sp_login, idp_unique_id) |
|
109 |
return results[0][1]['uniqueID'][0] |
|
110 | ||
111 |
@staticmethod |
|
112 |
def delete(asso_id): |
|
113 |
""" delete the association which has the following asso_id """ |
|
114 |
dn = "uniqueID=%s,%s" % (asso_id, config.ldap_base_dn) |
|
115 |
storage_conn.delete_s(dn) |
|
116 |
logger.info('Delete %r association', dn) |
|
117 | ||
118 |
@staticmethod |
|
119 |
def get_last_connected(sp_name, idp_unique_id, idp_name='default'): |
|
120 |
""" get the last connecting association which match the parameters |
|
121 |
return a dict of the association |
|
122 |
""" |
|
123 |
results = storage_conn.search_s(config.ldap_base_dn, ldap.SCOPE_ONELEVEL, |
|
124 |
filterstr='(&(objectClass=MandayeUser)(spName=%s)(idpUniqueID=%s)(idpName=%s))' % (sp_name, idp_unique_id, idp_name)) |
|
125 |
if results: |
|
126 |
return Association.ldap2association(results[0][1]) |
|
127 |
return None |
|
128 | ||
129 |
@staticmethod |
|
130 |
def update_last_connection(asso_id): |
|
131 |
""" update the association last conenction time with the current time |
|
132 |
return a dict of the association |
|
133 |
""" |
|
134 |
last_connection = datetime.datetime.utcnow().strftime("%Y%m%d%H%M%SZ") |
|
135 |
dn = "uniqueID=%s,%s" % (asso_id, config.ldap_base_dn) |
|
136 |
mod_list = [(ldap.MOD_REPLACE, 'lastConnection', last_connection)] |
|
137 |
storage_conn.modify_s(dn, mod_list) |
|
138 |
mandaye/dispatcher.py | ||
---|---|---|
146 | 146 |
elif self.req_mapping['response']: |
147 | 147 |
logger.debug("Loading response hook(s)") |
148 | 148 |
hook = self.req_mapping['response'] |
149 |
print hook
|
|
149 |
logger.debug('Using hook: %r', hook)
|
|
150 | 150 |
response = self._call_hook(hook, request, None) |
151 | 151 |
if not response: |
152 | 152 |
return _500(self.env["PATH_INFO"], "The response hook failed") |
mandaye/global_config.py | ||
---|---|---|
4 | 4 |
_PROJECT_PATH = os.path.join(os.path.dirname(__file__), '..') |
5 | 5 | |
6 | 6 |
# Choose storage |
7 |
# Only sql at the moment
|
|
7 |
# mandaye.backends.ldap_back or mandaye.backends.sql
|
|
8 | 8 |
storage_backend = "mandaye.backends.sql" |
9 | 9 | |
10 | 10 |
## SQL Backend config |
11 | 11 |
# Database configuration |
12 |
# http://docs.sqlalchemy.org/en/rel_0_7/core/engines.html
|
|
12 |
# http://docs.sqlalchemy.org/en/rel_0_8/core/engines.html
|
|
13 | 13 |
# rfc 1738 https://tools.ietf.org/html/rfc1738 |
14 | 14 |
# dialect+driver://username:password@host:port/database |
15 | 15 |
db_url = 'sqlite:///test.db' |
16 | 16 | |
17 |
## LDAP Backend config |
|
18 |
ldap_url = 'ldap://127.0.0.1' |
|
19 |
ldap_bind_dn = 'cn=admin,dc=acompany,dc=org' |
|
20 |
ldap_bind_password = 'MyPassword' |
|
21 |
ldap_base_dn = 'ou=mandaye,dc=acompany,dc=org' |
|
17 | 22 | |
18 | 23 |
# urllib2 debug mode |
19 | 24 |
debug = False |
mandaye/skel/example.module/config.py | ||
---|---|---|
30 | 30 |
# dialect+driver://username:password@host:port/database |
31 | 31 |
db_url = config.get('database', 'url') |
32 | 32 | |
33 |
## LDAP Backend config |
|
34 |
ldap_url = config.get('ldap', 'url') |
|
35 |
ldap_bind_dn = config.get('ldap', 'base_dn') |
|
36 |
ldap_bind_password = config.get('ldap', 'bind_password') |
|
37 |
ldap_base_dn = config.get('ldap', 'base_dn') |
|
38 | ||
33 | 39 |
debug = config.getboolean('debug', 'debug') |
34 | 40 | |
35 | 41 |
# Log configuration |
... | ... | |
133 | 139 |
auto_decompress = config.getboolean('mandaye', 'auto_decompress') |
134 | 140 |
# Ask mandaye to add a toolbar with Mandaye's links |
135 | 141 |
mandaye_toolbar = config.getboolean('mandaye', 'toolbar') |
136 |
mandaye_toolbar_offline = config.getboolean('mandaye', 'offline_toolbar')
|
|
142 |
mandaye_offline_toolbar= config.getboolean('mandaye', 'offline_toolbar')
|
|
137 | 143 |
# Authentic 2 auto connection |
138 | 144 |
a2_auto_connection = config.getboolean('mandaye', 'a2_auto_connection') |
139 | 145 | |
140 |
# Choose storage |
|
141 |
# Only mandaye.backends.sql at the moment |
|
146 |
# Choose storage (sql or ldap) |
|
142 | 147 |
if config.get('mandaye', 'storage_backend') == 'sql': |
143 | 148 |
storage_backend = "mandaye.backends.sql" |
149 |
elif config.get('mandaye', 'storage_backend') == 'ldap': |
|
150 |
storage_backend = "mandaye.backends.ldap_back" |
|
144 | 151 |
else: |
145 |
ImproperlyConfigured('Storage backend must be sql') |
|
152 |
ImproperlyConfigured('Storage backend must be sql or ldap')
|
|
146 | 153 | |
147 | 154 |
# Encrypt service provider passwords with a secret |
148 | 155 |
# You should install pycypto to use this feature |
mandaye/skel/example.module/default-config.ini | ||
---|---|---|
2 | 2 |
base_dir: . |
3 | 3 | |
4 | 4 |
[database] |
5 |
; use by sql backend |
|
5 | 6 |
; http://docs.sqlalchemy.org/en/rel_0_8/core/engines.html |
6 | 7 |
url: sqlite:///%(base_dir)s/{project_name}.db |
7 | 8 | |
9 |
[ldap] |
|
10 |
; use by ldap backend |
|
11 |
url: ldap://127.0.0.1 |
|
12 |
bind_dn: cn=admin,dc=acompany,dc=org |
|
13 |
bind_password: AdminPassword |
|
14 |
base_dn: ou=mandaye,dc=acompany,dc=org |
|
15 | ||
8 | 16 |
[dirs] |
9 | 17 |
config_root: %(base_dir)s/conf.d |
10 | 18 |
data_dir: %(base_dir)s/data |
... | ... | |
23 | 31 |
toolbar: true |
24 | 32 |
offline_toolbar: true |
25 | 33 |
a2_auto_connection: false |
26 |
; only sql at the moment
|
|
34 |
; sql or ldap
|
|
27 | 35 |
storage_backend: sql |
28 | 36 |
auto_decompress: true |
29 | 37 |
; if you want to encypt password set to true |
30 |
- |