Files @ aaa7c3331186
Branch filter:

Location: kallithea/rhodecode/lib/auth_modules/__init__.py - annotation

Bradley M. Kuhn
Rename paster command setup-rhodecode to setup-db
  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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
ffd45b185016
# -*- coding: utf-8 -*-
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
Authentication modules
"""

import logging
import traceback

from rhodecode.lib.compat import importlib
from rhodecode.lib.utils2 import str2bool
from rhodecode.lib.compat import formatted_json, hybrid_property
from rhodecode.lib.auth import PasswordGenerator
from rhodecode.model.user import UserModel
from rhodecode.model.db import RhodeCodeSetting, User, UserGroup
from rhodecode.model.meta import Session
from rhodecode.model.user_group import UserGroupModel

log = logging.getLogger(__name__)


class LazyFormencode(object):
    def __init__(self, formencode_obj, *args, **kwargs):
        self.formencode_obj = formencode_obj
        self.args = args
        self.kwargs = kwargs

    def __call__(self, *args, **kwargs):
        from inspect import isfunction
        formencode_obj = self.formencode_obj
        if isfunction(formencode_obj):
            #case we wrap validators into functions
            formencode_obj = self.formencode_obj(*args, **kwargs)
        return formencode_obj(*self.args, **self.kwargs)


class RhodeCodeAuthPluginBase(object):
    auth_func_attrs = {
        "username": "unique username",
        "firstname": "first name",
        "lastname": "last name",
        "email": "email address",
        "groups": '["list", "of", "groups"]',
        "extern_name": "name in external source of record",
        "extern_type": "type of external source of record",
        "admin": 'True|False defines if user should be RhodeCode super admin',
        "active": 'True|False defines active state of user internally for RhodeCode',
        "active_from_extern": "True|False\None, active state from the external auth, "
                              "None means use definition from RhodeCode extern_type active value"
    }

    @property
    def validators(self):
        """
        Exposes RhodeCode validators modules
        """
        # this is a hack to overcome issues with pylons threadlocals and
        # translator object _() not beein registered properly.
        class LazyCaller(object):
            def __init__(self, name):
                self.validator_name = name

            def __call__(self, *args, **kwargs):
                from rhodecode.model import validators as v
                obj = getattr(v, self.validator_name)
                #log.debug('Initializing lazy formencode object: %s' % obj)
                return LazyFormencode(obj, *args, **kwargs)


        class ProxyGet(object):
            def __getattribute__(self, name):
                return LazyCaller(name)

        return ProxyGet()

    @hybrid_property
    def name(self):
        """
        Returns the name of this authentication plugin.

        :returns: string
        """
        raise NotImplementedError("Not implemented in base class")

    @hybrid_property
    def is_container_auth(self):
        """
        Returns bool if this module uses container auth.

        This property will trigger an automatic call to authenticate on
        a visit to the website or during a push/pull.

        :returns: bool
        """
        return False

    def accepts(self, user, accepts_empty=True):
        """
        Checks if this authentication module should accept a request for
        the current user.

        :param user: user object fetched using plugin's get_user() method.
        :param accepts_empty: if True accepts don't allow the user to be empty
        :returns: boolean
        """
        plugin_name = self.name
        if not user and not accepts_empty:
            log.debug('User is empty not allowed to authenticate')
            return False

        if user and user.extern_type and user.extern_type != plugin_name:
            log.debug('User %s should authenticate using %s this is %s, skipping'
                      % (user, user.extern_type, plugin_name))

            return False
        return True

    def get_user(self, username=None, **kwargs):
        """
        Helper method for user fetching in plugins, by default it's using
        simple fetch by username, but this method can be custimized in plugins
        eg. container auth plugin to fetch user by environ params

        :param username: username if given to fetch from database
        :param kwargs: extra arguments needed for user fetching.
        """
        user = None
        log.debug('Trying to fetch user `%s` from RhodeCode database'
                  % (username))
        if username:
            user = User.get_by_username(username)
            if not user:
                log.debug('Fallback to fetch user in case insensitive mode')
                user = User.get_by_username(username, case_insensitive=True)
        else:
            log.debug('provided username:`%s` is empty skipping...' % username)
        return user

    def settings(self):
        """
        Return a list of the form:
        [
            {
                "name": "OPTION_NAME",
                "type": "[bool|password|string|int|select]",
                ["values": ["opt1", "opt2", ...]]
                "validator": "expr"
                "description": "A short description of the option" [,
                "default": Default Value],
                ["formname": "Friendly Name for Forms"]
            } [, ...]
        ]

        This is used to interrogate the authentication plugin as to what
        settings it expects to be present and configured.

        'type' is a shorthand notation for what kind of value this option is.
        This is primarily used by the auth web form to control how the option
        is configured.
                bool : checkbox
                password : password input box
                string : input box
                select : single select dropdown

        'validator' is an lazy instantiated form field validator object, ala
        formencode. You need to *call* this object to init the validators.
        All calls to RhodeCode validators should be used through self.validators
        which is a lazy loading proxy of formencode module.
        """
        raise NotImplementedError("Not implemented in base class")

    def plugin_settings(self):
        """
        This method is called by the authentication framework, not the .settings()
        method. This method adds a few default settings (e.g., "active"), so that
        plugin authors don't have to maintain a bunch of boilerplate.

        OVERRIDING THIS METHOD WILL CAUSE YOUR PLUGIN TO FAIL.
        """

        rcsettings = self.settings()
        rcsettings.insert(0, {
            "name": "enabled",
            "validator": self.validators.StringBoolean(if_missing=False),
            "type": "bool",
            "description": "Enable or Disable this Authentication Plugin",
            "formname": "Enabled"
            }
        )
        return rcsettings

    def user_activation_state(self):
        """
        Defines user activation state when creating new users

        :returns: boolean
        """
        raise NotImplementedError("Not implemented in base class")

    def auth(self, userobj, username, passwd, settings, **kwargs):
        """
        Given a user object (which may be null), username, a plaintext password,
        and a settings object (containing all the keys needed as listed in settings()),
        authenticate this user's login attempt.

        Return None on failure. On success, return a dictionary of the form:

            see: RhodeCodeAuthPluginBase.auth_func_attrs
        This is later validated for correctness
        """
        raise NotImplementedError("not implemented in base class")

    def _authenticate(self, userobj, username, passwd, settings, **kwargs):
        """
        Wrapper to call self.auth() that validates call on it

        :param userobj: userobj
        :param username: username
        :param passwd: plaintext password
        :param settings: plugin settings
        """
        auth = self.auth(userobj, username, passwd, settings, **kwargs)
        if auth:
            return self._validate_auth_return(auth)
        return auth

    def _validate_auth_return(self, ret):
        if not isinstance(ret, dict):
            raise Exception('returned value from auth must be a dict')
        for k in self.auth_func_attrs:
            if k not in ret:
                raise Exception('Missing %s attribute from returned data' % k)
        return ret


class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
    def use_fake_password(self):
        """
        Return a boolean that indicates whether or not we should set the user's
        password to a random value when it is authenticated by this plugin.
        If your plugin provides authentication, then you will generally want this.

        :returns: boolean
        """
        raise NotImplementedError("Not implemented in base class")

    def _authenticate(self, userobj, username, passwd, settings, **kwargs):
        auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
            userobj, username, passwd, settings, **kwargs)
        if auth:
            # maybe plugin will clean the username ?
            # we should use the return value
            username = auth['username']
            # if user is not active from our extern type we should fail to authe
            # this can prevent from creating users in RhodeCode when using
            # external authentication, but if it's inactive user we shouldn't
            # create that user anyway
            if auth['active_from_extern'] is False:
                log.warning("User %s authenticated against %s, but is inactive"
                            % (username, self.__module__))
                return None

            if self.use_fake_password():
                # Randomize the PW because we don't need it, but don't want
                # them blank either
                passwd = PasswordGenerator().gen_password(length=8)

            log.debug('Updating or creating user info from %s plugin'
                      % self.name)
            user = UserModel().create_or_update(
                username=username,
                password=passwd,
                email=auth["email"],
                firstname=auth["firstname"],
                lastname=auth["lastname"],
                active=auth["active"],
                admin=auth["admin"],
                extern_name=auth["extern_name"],
                extern_type=self.name
            )
            Session().flush()
            # enforce user is just in given groups, all of them has to be ones
            # created from plugins. We store this info in _group_data JSON field
            try:
                groups = auth['groups'] or []
                UserGroupModel().enforce_groups(user, groups, self.name)
            except Exception:
                # for any reason group syncing fails, we should proceed with login
                log.error(traceback.format_exc())
            Session().commit()
        return auth


def importplugin(plugin):
    """
    Imports and returns the authentication plugin in the module named by plugin
    (e.g., plugin='rhodecode.lib.auth_modules.auth_rhodecode'). Returns the
    RhodeCodeAuthPluginBase subclass on success, raises exceptions on failure.

    raises:
        AttributeError -- no RhodeCodeAuthPlugin class in the module
        TypeError -- if the RhodeCodeAuthPlugin is not a subclass of ours RhodeCodeAuthPluginBase
        ImportError -- if we couldn't import the plugin at all
    """
    log.debug("Importing %s" % plugin)
    PLUGIN_CLASS_NAME = "RhodeCodeAuthPlugin"
    try:
        module = importlib.import_module(plugin)
    except (ImportError, TypeError):
        log.error(traceback.format_exc())
        # TODO: make this more error prone, if by some accident we screw up
        # the plugin name, the crash is preatty bad and hard to recover
        raise

    log.debug("Loaded auth plugin from %s (module:%s, file:%s)"
              % (plugin, module.__name__, module.__file__))

    pluginclass = getattr(module, PLUGIN_CLASS_NAME)
    if not issubclass(pluginclass, RhodeCodeAuthPluginBase):
        raise TypeError("Authentication class %s.RhodeCodeAuthPlugin is not "
                        "a subclass of %s" % (plugin, RhodeCodeAuthPluginBase))
    return pluginclass


def loadplugin(plugin):
    """
    Loads and returns an instantiated authentication plugin.

        see: importplugin
    """
    plugin = importplugin(plugin)()
    if plugin.plugin_settings.im_func != RhodeCodeAuthPluginBase.plugin_settings.im_func:
        raise TypeError("Authentication class %s.RhodeCodeAuthPluginBase "
                        "has overriden the plugin_settings method, which is "
                        "forbidden." % plugin)
    return plugin


def authenticate(username, password, environ=None):
    """
    Authentication function used for access control,
    It tries to authenticate based on enabled authentication modules.

    :param username: username can be empty for container auth
    :param password: password can be empty for container auth
    :param environ: environ headers passed for container auth
    :returns: None if auth failed, plugin_user dict if auth is correct
    """

    auth_plugins = RhodeCodeSetting.get_auth_plugins()
    log.debug('Authentication against %s plugins' % (auth_plugins,))
    for module in auth_plugins:
        try:
            plugin = loadplugin(module)
        except (ImportError, AttributeError, TypeError), e:
            raise ImportError('Failed to load authentication module %s : %s'
                              % (module, str(e)))
        log.debug('Trying authentication using ** %s **' % (module,))
        # load plugin settings from RhodeCode database
        plugin_name = plugin.name
        plugin_settings = {}
        for v in plugin.plugin_settings():
            conf_key = "auth_%s_%s" % (plugin_name, v["name"])
            setting = RhodeCodeSetting.get_by_name(conf_key)
            plugin_settings[v["name"]] = setting.app_settings_value if setting else None
        log.debug('Plugin settings \n%s' % formatted_json(plugin_settings))

        if not str2bool(plugin_settings["enabled"]):
            log.info("Authentication plugin %s is disabled, skipping for %s"
                     % (module, username))
            continue

        # use plugin's method of user extraction.
        user = plugin.get_user(username, environ=environ,
                               settings=plugin_settings)
        log.debug('Plugin %s extracted user is `%s`' % (module, user))
        if not plugin.accepts(user):
            log.debug('Plugin %s does not accept user `%s` for authentication'
                      % (module, user))
            continue
        else:
            log.debug('Plugin %s accepted user `%s` for authentication'
                      % (module, user))

        log.info('Authenticating user using %s plugin' % plugin.__module__)
        # _authenticate is a wrapper for .auth() method of plugin.
        # it checks if .auth() sends proper data. for RhodeCodeExternalAuthPlugin
        # it also maps users to Database and maps the attributes returned
        # from .auth() to RhodeCode database. If this function returns data
        # then auth is correct.
        plugin_user = plugin._authenticate(user, username, password,
                                           plugin_settings,
                                           environ=environ or {})
        log.debug('PLUGIN USER DATA: %s' % plugin_user)

        if plugin_user:
            log.debug('Plugin returned proper authentication data')
            return plugin_user

        # we failed to Auth because .auth() method didn't return proper the user
        log.warning("User `%s` failed to authenticate against %s"
                    % (username, plugin.__module__))
    return None