aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/resource/backends/ldap.py
blob: 436840353a3191efc58cba0436fd01fa65855f32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# 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.

from __future__ import absolute_import

import uuid

from oslo_config import cfg
from oslo_log import log

from keystone.common import clean
from keystone.common import driver_hints
from keystone.common import ldap as common_ldap
from keystone.common import models
from keystone import exception
from keystone.i18n import _
from keystone.identity.backends import ldap as ldap_identity
from keystone import resource


CONF = cfg.CONF
LOG = log.getLogger(__name__)


class Resource(resource.Driver):
    def __init__(self):
        super(Resource, self).__init__()
        self.LDAP_URL = CONF.ldap.url
        self.LDAP_USER = CONF.ldap.user
        self.LDAP_PASSWORD = CONF.ldap.password
        self.suffix = CONF.ldap.suffix

        # This is the only deep dependency from resource back to identity.
        # This is safe to do since if you are using LDAP for resource, it is
        # required that you are using it for identity as well.
        self.user = ldap_identity.UserApi(CONF)

        self.project = ProjectApi(CONF)

    def default_assignment_driver(self):
        return 'ldap'

    def _set_default_parent_project(self, ref):
        """If the parent project ID has not been set, set it to None."""
        if isinstance(ref, dict):
            if 'parent_id' not in ref:
                ref = dict(ref, parent_id=None)
            return ref
        elif isinstance(ref, list):
            return [self._set_default_parent_project(x) for x in ref]
        else:
            raise ValueError(_('Expected dict or list: %s') % type(ref))

    def _set_default_is_domain_project(self, ref):
        if isinstance(ref, dict):
            return dict(ref, is_domain=False)
        elif isinstance(ref, list):
            return [self._set_default_is_domain_project(x) for x in ref]
        else:
            raise ValueError(_('Expected dict or list: %s') % type(ref))

    def _validate_parent_project_is_none(self, ref):
        """If a parent_id different from None was given,
           raises InvalidProjectException.

        """
        parent_id = ref.get('parent_id')
        if parent_id is not None:
            raise exception.InvalidParentProject(parent_id)

    def _validate_is_domain_field_is_false(self, ref):
        is_domain = ref.pop('is_domain', None)
        if is_domain:
            raise exception.ValidationError(_('LDAP does not support projects '
                                              'with is_domain flag enabled'))

    def _set_default_attributes(self, project_ref):
        project_ref = self._set_default_domain(project_ref)
        project_ref = self._set_default_is_domain_project(project_ref)
        return self._set_default_parent_project(project_ref)

    def get_project(self, tenant_id):
        return self._set_default_attributes(
            self.project.get(tenant_id))

    def list_projects(self, hints):
        return self._set_default_attributes(
            self.project.get_all_filtered(hints))

    def list_projects_in_domain(self, domain_id):
        # We don't support multiple domains within this driver, so ignore
        # any domain specified
        return self.list_projects(driver_hints.Hints())

    def list_projects_in_subtree(self, project_id):
        # We don't support projects hierarchy within this driver, so a
        # project will never have children
        return []

    def list_project_parents(self, project_id):
        # We don't support projects hierarchy within this driver, so a
        # project will never have parents
        return []

    def is_leaf_project(self, project_id):
        # We don't support projects hierarchy within this driver, so a
        # project will always be a root and a leaf at the same time
        return True

    def list_projects_from_ids(self, ids):
        return [self.get_project(id) for id in ids]

    def list_project_ids_from_domain_ids(self, domain_ids):
        # We don't support multiple domains within this driver, so ignore
        # any domain specified
        return [x.id for x in self.list_projects(driver_hints.Hints())]

    def get_project_by_name(self, tenant_name, domain_id):
        self._validate_default_domain_id(domain_id)
        return self._set_default_attributes(
            self.project.get_by_name(tenant_name))

    def create_project(self, tenant_id, tenant):
        self.project.check_allow_create()
        self._validate_parent_project_is_none(tenant)
        self._validate_is_domain_field_is_false(tenant)
        tenant['name'] = clean.project_name(tenant['name'])
        data = tenant.copy()
        if 'id' not in data or data['id'] is None:
            data['id'] = str(uuid.uuid4().hex)
        if 'description' in data and data['description'] in ['', None]:
            data.pop('description')
        return self._set_default_attributes(
            self.project.create(data))

    def update_project(self, tenant_id, tenant):
        self.project.check_allow_update()
        tenant = self._validate_default_domain(tenant)
        self._validate_is_domain_field_is_false(tenant)
        if 'name' in tenant:
            tenant['name'] = clean.project_name(tenant['name'])
        return self._set_default_attributes(
            self.project.update(tenant_id, tenant))

    def delete_project(self, tenant_id):
        self.project.check_allow_delete()
        if self.project.subtree_delete_enabled:
            self.project.deleteTree(tenant_id)
        else:
            # The manager layer will call assignments to delete the
            # role assignments, so we just have to delete the project itself.
            self.project.delete(tenant_id)

    def create_domain(self, domain_id, domain):
        if domain_id == CONF.identity.default_domain_id:
            msg = _('Duplicate ID, %s.') % domain_id
            raise exception.Conflict(type='domain', details=msg)
        raise exception.Forbidden(_('Domains are read-only against LDAP'))

    def get_domain(self, domain_id):
        self._validate_default_domain_id(domain_id)
        return resource.calc_default_domain()

    def update_domain(self, domain_id, domain):
        self._validate_default_domain_id(domain_id)
        raise exception.Forbidden(_('Domains are read-only against LDAP'))

    def delete_domain(self, domain_id):
        self._validate_default_domain_id(domain_id)
        raise exception.Forbidden(_('Domains are read-only against LDAP'))

    def list_domains(self, hints):
        return [resource.calc_default_domain()]

    def list_domains_from_ids(self, ids):
        return [resource.calc_default_domain()]

    def get_domain_by_name(self, domain_name):
        default_domain = resource.calc_default_domain()
        if domain_name != default_domain['name']:
            raise exception.DomainNotFound(domain_id=domain_name)
        return default_domain


# TODO(termie): turn this into a data object and move logic to driver
class ProjectApi(common_ldap.ProjectLdapStructureMixin,
                 common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap):

    model = models.Project

    def create(self, values):
        data = values.copy()
        if data.get('id') is None:
            data['id'] = uuid.uuid4().hex
        return super(ProjectApi, self).create(data)

    def update(self, project_id, values):
        old_obj = self.get(project_id)
        return super(ProjectApi, self).update(project_id, values, old_obj)

    def get_all_filtered(self, hints):
        query = self.filter_query(hints)
        return super(ProjectApi, self).get_all(query)