##// END OF EJS Templates
authn: Add rhodecode token auth plugin.
johbo -
r79:bfb1a9cf default
parent child Browse files
Show More
@@ -0,0 +1,134 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 RhodeCode authentication token plugin for built in internal auth
23 """
24
25 import logging
26
27 from sqlalchemy.ext.hybrid import hybrid_property
28
29 from rhodecode.translation import _
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, VCS_TYPE
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
32 from rhodecode.model.db import User, UserApiKeys
33
34
35 log = logging.getLogger(__name__)
36
37
38 def plugin_factory(plugin_id, *args, **kwds):
39 plugin = RhodeCodeAuthPlugin(plugin_id)
40 return plugin
41
42
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
44 pass
45
46
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
48 """
49 Enables usage of authentication tokens for vcs operations.
50 """
51
52 def includeme(self, config):
53 config.add_authn_plugin(self)
54 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
55 config.add_view(
56 'rhodecode.authentication.views.AuthnPluginViewBase',
57 attr='settings_get',
58 request_method='GET',
59 route_name='auth_home',
60 context=RhodecodeAuthnResource)
61 config.add_view(
62 'rhodecode.authentication.views.AuthnPluginViewBase',
63 attr='settings_post',
64 request_method='POST',
65 route_name='auth_home',
66 context=RhodecodeAuthnResource)
67
68 def get_display_name(self):
69 return _('Rhodecode Token Auth')
70
71 @hybrid_property
72 def name(self):
73 return "authtoken"
74
75 def user_activation_state(self):
76 def_user_perms = User.get_default_user().AuthUser.permissions['global']
77 return 'hg.register.auto_activate' in def_user_perms
78
79 def allows_authentication_from(
80 self, user, allows_non_existing_user=True,
81 allowed_auth_plugins=None, allowed_auth_sources=None):
82 """
83 Custom method for this auth that doesn't accept empty users. And also
84 allows rhodecode and authtoken extern_type to auth with this. But only
85 via vcs mode
86 """
87 # only this and rhodecode plugins can use this type
88 from rhodecode.authentication.plugins import auth_rhodecode
89 allowed_auth_plugins = [
90 self.name, auth_rhodecode.RhodeCodeAuthPlugin.name]
91 # only for vcs operations
92 allowed_auth_sources = [VCS_TYPE]
93
94 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
95 user, allows_non_existing_user=False,
96 allowed_auth_plugins=allowed_auth_plugins,
97 allowed_auth_sources=allowed_auth_sources)
98
99 def auth(self, userobj, username, password, settings, **kwargs):
100 if not userobj:
101 log.debug('userobj was:%s skipping' % (userobj, ))
102 return None
103
104 user_attrs = {
105 "username": userobj.username,
106 "firstname": userobj.firstname,
107 "lastname": userobj.lastname,
108 "groups": [],
109 "email": userobj.email,
110 "admin": userobj.admin,
111 "active": userobj.active,
112 "active_from_extern": userobj.active,
113 "extern_name": userobj.user_id,
114 "extern_type": userobj.extern_type,
115 }
116
117 log.debug('Authenticating user with args %s', user_attrs)
118 if userobj.active:
119 role = UserApiKeys.ROLE_VCS
120 active_tokens = [x.api_key for x in
121 User.extra_valid_auth_tokens(userobj, role=role)]
122 if userobj.username == username and password in active_tokens:
123 log.info(
124 'user `%s` successfully authenticated via %s',
125 user_attrs['username'], self.name)
126 return user_attrs
127 log.error(
128 'user `%s` failed to authenticate via %s, reason: bad or '
129 'inactive token.', username, self.name)
130 else:
131 log.warning(
132 'user `%s` failed to authenticate via %s, reason: account not '
133 'active.', username, self.name)
134 return None
@@ -1,223 +1,223 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import colander
22 22 import logging
23 23
24 24 from sqlalchemy.ext.hybrid import hybrid_property
25 25
26 26 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
27 27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
28 28 from rhodecode.authentication.routes import AuthnPluginResourceBase
29 29 from rhodecode.lib.colander_utils import strip_whitespace
30 30 from rhodecode.lib.utils2 import str2bool, safe_unicode
31 31 from rhodecode.model.db import User
32 32 from rhodecode.translation import _
33 33
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 def plugin_factory(plugin_id, *args, **kwds):
39 39 """
40 40 Factory function that is called during plugin discovery.
41 41 It returns the plugin instance.
42 42 """
43 43 plugin = RhodeCodeAuthPlugin(plugin_id)
44 44 return plugin
45 45
46 46
47 47 class HeadersAuthnResource(AuthnPluginResourceBase):
48 48 pass
49 49
50 50
51 51 class HeadersSettingsSchema(AuthnPluginSettingsSchemaBase):
52 52 header = colander.SchemaNode(
53 53 colander.String(),
54 54 default='REMOTE_USER',
55 55 description=_('Header to extract the user from'),
56 56 preparer=strip_whitespace,
57 57 title=_('Header'),
58 58 widget='string')
59 59 fallback_header = colander.SchemaNode(
60 60 colander.String(),
61 61 default='HTTP_X_FORWARDED_USER',
62 62 description=_('Header to extract the user from when main one fails'),
63 63 preparer=strip_whitespace,
64 64 title=_('Fallback header'),
65 65 widget='string')
66 66 clean_username = colander.SchemaNode(
67 67 colander.Boolean(),
68 68 default=True,
69 69 description=_('Perform cleaning of user, if passed user has @ in '
70 70 'username then first part before @ is taken. '
71 71 'If there\'s \\ in the username only the part after '
72 72 ' \\ is taken'),
73 73 missing=False,
74 74 title=_('Clean username'),
75 75 widget='bool')
76 76
77 77
78 78 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
79 79
80 80 def includeme(self, config):
81 81 config.add_authn_plugin(self)
82 82 config.add_authn_resource(self.get_id(), HeadersAuthnResource(self))
83 83 config.add_view(
84 84 'rhodecode.authentication.views.AuthnPluginViewBase',
85 85 attr='settings_get',
86 86 request_method='GET',
87 87 route_name='auth_home',
88 88 context=HeadersAuthnResource)
89 89 config.add_view(
90 90 'rhodecode.authentication.views.AuthnPluginViewBase',
91 91 attr='settings_post',
92 92 request_method='POST',
93 93 route_name='auth_home',
94 94 context=HeadersAuthnResource)
95 95
96 96 def get_display_name(self):
97 97 return _('Headers')
98 98
99 99 def get_settings_schema(self):
100 100 return HeadersSettingsSchema()
101 101
102 102 @hybrid_property
103 103 def name(self):
104 104 return 'headers'
105 105
106 106 @hybrid_property
107 107 def is_container_auth(self):
108 108 return True
109 109
110 110 def use_fake_password(self):
111 111 return True
112 112
113 113 def user_activation_state(self):
114 114 def_user_perms = User.get_default_user().AuthUser.permissions['global']
115 115 return 'hg.extern_activate.auto' in def_user_perms
116 116
117 117 def _clean_username(self, username):
118 118 # Removing realm and domain from username
119 119 username = username.split('@')[0]
120 120 username = username.rsplit('\\')[-1]
121 121 return username
122 122
123 123 def _get_username(self, environ, settings):
124 124 username = None
125 125 environ = environ or {}
126 126 if not environ:
127 127 log.debug('got empty environ: %s' % environ)
128 128
129 129 settings = settings or {}
130 130 if settings.get('header'):
131 131 header = settings.get('header')
132 132 username = environ.get(header)
133 133 log.debug('extracted %s:%s' % (header, username))
134 134
135 135 # fallback mode
136 136 if not username and settings.get('fallback_header'):
137 137 header = settings.get('fallback_header')
138 138 username = environ.get(header)
139 139 log.debug('extracted %s:%s' % (header, username))
140 140
141 141 if username and str2bool(settings.get('clean_username')):
142 142 log.debug('Received username `%s` from headers' % username)
143 143 username = self._clean_username(username)
144 144 log.debug('New cleanup user is:%s' % username)
145 145 return username
146 146
147 147 def get_user(self, username=None, **kwargs):
148 148 """
149 149 Helper method for user fetching in plugins, by default it's using
150 150 simple fetch by username, but this method can be custimized in plugins
151 151 eg. headers auth plugin to fetch user by environ params
152 152 :param username: username if given to fetch
153 153 :param kwargs: extra arguments needed for user fetching.
154 154 """
155 155 environ = kwargs.get('environ') or {}
156 156 settings = kwargs.get('settings') or {}
157 157 username = self._get_username(environ, settings)
158 158 # we got the username, so use default method now
159 159 return super(RhodeCodeAuthPlugin, self).get_user(username)
160 160
161 161 def auth(self, userobj, username, password, settings, **kwargs):
162 162 """
163 163 Get's the headers_auth username (or email). It tries to get username
164 164 from REMOTE_USER if this plugin is enabled, if that fails
165 165 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
166 166 is set. clean_username extracts the username from this data if it's
167 167 having @ in it.
168 168 Return None on failure. On success, return a dictionary of the form:
169 169
170 170 see: RhodeCodeAuthPluginBase.auth_func_attrs
171 171
172 172 :param userobj:
173 173 :param username:
174 174 :param password:
175 175 :param settings:
176 176 :param kwargs:
177 177 """
178 178 environ = kwargs.get('environ')
179 179 if not environ:
180 180 log.debug('Empty environ data skipping...')
181 181 return None
182 182
183 183 if not userobj:
184 184 userobj = self.get_user('', environ=environ, settings=settings)
185 185
186 186 # we don't care passed username/password for headers auth plugins.
187 187 # only way to log in is using environ
188 188 username = None
189 189 if userobj:
190 190 username = getattr(userobj, 'username')
191 191
192 192 if not username:
193 # we don't have any objects in DB user doesn't exist extrac username
194 # from environ based on the settings
193 # we don't have any objects in DB user doesn't exist extract
194 # username from environ based on the settings
195 195 username = self._get_username(environ, settings)
196 196
197 197 # if cannot fetch username, it's a no-go for this plugin to proceed
198 198 if not username:
199 199 return None
200 200
201 201 # old attrs fetched from RhodeCode database
202 202 admin = getattr(userobj, 'admin', False)
203 203 active = getattr(userobj, 'active', True)
204 204 email = getattr(userobj, 'email', '')
205 205 firstname = getattr(userobj, 'firstname', '')
206 206 lastname = getattr(userobj, 'lastname', '')
207 207 extern_type = getattr(userobj, 'extern_type', '')
208 208
209 209 user_attrs = {
210 210 'username': username,
211 211 'firstname': safe_unicode(firstname or username),
212 212 'lastname': safe_unicode(lastname or ''),
213 213 'groups': [],
214 214 'email': email or '',
215 215 'admin': admin or False,
216 216 'active': active,
217 217 'active_from_extern': True,
218 218 'extern_name': username,
219 219 'extern_type': extern_type,
220 220 }
221 221
222 222 log.info('user `%s` authenticated correctly' % user_attrs['username'])
223 223 return user_attrs
General Comments 0
You need to be logged in to leave comments. Login now