Projet

Général

Profil

0001-backends-implement-a-new-ldap-storage-backend.patch

Jérôme Schneider, 15 septembre 2014 10:52

Télécharger (13,4 ko)

Voir les différences:

Subject: [PATCH] backends: implement a new ldap storage backend

 ldap/mandaye.schema                            |  93 +++++++++++++++++
 mandaye/backends/default.py                    |   5 +
 mandaye/backends/ldap_back.py                  | 137 +++++++++++++++++++++++++
 mandaye/global_config.py                       |   9 +-
 mandaye/skel/example.module/config.py          |  13 ++-
 mandaye/skel/example.module/default-config.ini |  10 +-
 6 files changed, 261 insertions(+), 6 deletions(-)
 create mode 100644 ldap/mandaye.schema
 create mode 100644 mandaye/backends/ldap_back.py
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 'lastConnectionDate'
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 ( lastConnectionDate ) )
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 dictionary 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['lastConnectionDate'][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 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
        """ check the given user is present in the directory """
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
        results = storage_conn.search_s(config.ldap_base_dn, ldap.SCOPE_ONELEVEL,
80
                filterstr='(&(objectClass=MandayeUser)(spName=%s)(spLogin=%s)(idpUniqueID=%s)(idpName=%s))' %\
81
                        (sp_name, sp_login, idp_unique_id, idp_name))
82
        if not results:
83
            association = {'spName': sp_name,
84
                    'spLogin': sp_login,
85
                    'spPostValues': sp_post_values,
86
                    'idpUniqueID': idp_unique_id,
87
                    'idpName': idp_name,
88
                    'creationDate': datetime.datetime.utcnow().strftime('%Y%m%d%H%M%SZ'),
89
                    'lastConnectionDate': datetime.datetime.utcnow().strftime('%Y%m%d%H%M%SZ'),
90
                    'objectClass': 'MandayeUser'
91
                    }
92
            mod_list = ldap.modlist.addModlist(association)
93
            while True:
94
                unique_id = random.randint(1, 5000000)
95
                dn = "uniqueID=%s,%s" % (unique_id, config.ldap_base_dn)
96
                try:
97
                    result = storage_conn.add_s(dn, mod_list)
98
                except ldap.ALREADY_EXISTS:
99
                    continue
100
                break
101
            logger.info("New association %r with %r", sp_login, idp_unique_id)
102
            return unique_id
103
        else:
104
            dn = results[0][0]
105
            mod_list = [(ldap.MOD_REPLACE, 'spPostValues', sp_post_values)]
106
            storage_conn.modify_s(dn, mod_list)
107
            logger.info("Update post values for %r (%r)", sp_login, idp_unique_id)
108
        return results[0][1]['uniqueID'][0]
109

  
110
    @staticmethod
111
    def delete(asso_id):
112
        """ delete the association which has the following asso_id """
113
        dn = "uniqueID=%s,%s" % (asso_id, config.ldap_base_dn)
114
        storage_conn.delete_s(dn)
115
        logger.info('Delete %r association', dn)
116

  
117
    @staticmethod
118
    def get_last_connected(sp_name, idp_unique_id, idp_name='default'):
119
        """ get the last connecting association which match the parameters
120
        return a dict of the association
121
        """
122
        results = storage_conn.search_s(config.ldap_base_dn, ldap.SCOPE_ONELEVEL,
123
                filterstr='(&(objectClass=MandayeUser)(spName=%s)(idpUniqueID=%s)(idpName=%s))' % (sp_name, idp_unique_id, idp_name))
124
        if results:
125
            return Association.ldap2association(results[0][1])
126
        return None
127

  
128
    @staticmethod
129
    def update_last_connection(asso_id):
130
        """ update the association last connection time with the current time
131
        return a dict of the association
132
        """
133
        last_connection = datetime.datetime.utcnow().strftime("%Y%m%d%H%M%SZ")
134
        dn = "uniqueID=%s,%s" % (asso_id, config.ldap_base_dn)
135
        mod_list = [(ldap.MOD_REPLACE, 'lastConnectionDate', last_connection)]
136
        storage_conn.modify_s(dn, mod_list)
137

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