##// END OF EJS Templates
registration: add a way to disable registration
ergo -
Show More
@@ -1,217 +1,222 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
3 # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
4 #
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
7 # You may obtain a copy of the License at
8 #
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
10 #
11 # Unless required by applicable law or agreed to in writing, software
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
15 # limitations under the License.
16
16
17 import datetime
17 import datetime
18 import logging
18 import logging
19 import pyelasticsearch
19 import pyelasticsearch
20 import redis
20 import redis
21 import os
21 import os
22 from pkg_resources import iter_entry_points
22 from pkg_resources import iter_entry_points
23
23
24 import appenlight.lib.jinja2_filters as jinja2_filters
24 import appenlight.lib.jinja2_filters as jinja2_filters
25 import appenlight.lib.encryption as encryption
25 import appenlight.lib.encryption as encryption
26
26
27 from pyramid.config import PHASE3_CONFIG
27 from pyramid.config import PHASE3_CONFIG
28 from pyramid.authentication import AuthTktAuthenticationPolicy
28 from pyramid.authentication import AuthTktAuthenticationPolicy
29 from pyramid.authorization import ACLAuthorizationPolicy
29 from pyramid.authorization import ACLAuthorizationPolicy
30 from pyramid_mailer.mailer import Mailer
30 from pyramid_mailer.mailer import Mailer
31 from pyramid.renderers import JSON
31 from pyramid.renderers import JSON
32 from pyramid_redis_sessions import session_factory_from_settings
32 from pyramid_redis_sessions import session_factory_from_settings
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.security import AllPermissionsList
34 from pyramid.security import AllPermissionsList
35 from pyramid_authstack import AuthenticationStackPolicy
35 from pyramid_authstack import AuthenticationStackPolicy
36 from redlock import Redlock
36 from redlock import Redlock
37 from sqlalchemy import engine_from_config
37 from sqlalchemy import engine_from_config
38
38
39 from appenlight.celery import configure_celery
39 from appenlight.celery import configure_celery
40 from appenlight.lib.configurator import (CythonCompatConfigurator,
40 from appenlight.lib.configurator import (CythonCompatConfigurator,
41 register_appenlight_plugin)
41 register_appenlight_plugin)
42 from appenlight.lib import cache_regions
42 from appenlight.lib import cache_regions
43 from appenlight.lib.ext_json import json
43 from appenlight.lib.ext_json import json
44 from appenlight.security import groupfinder, AuthTokenAuthenticationPolicy
44 from appenlight.security import groupfinder, AuthTokenAuthenticationPolicy
45
45
46 __license__ = 'Apache 2.0'
46 __license__ = 'Apache 2.0'
47 __author__ = 'RhodeCode GmbH'
47 __author__ = 'RhodeCode GmbH'
48 __url__ = 'http://rhodecode.com'
48 __url__ = 'http://rhodecode.com'
49
49
50 json_renderer = JSON(serializer=json.dumps, indent=4)
50 json_renderer = JSON(serializer=json.dumps, indent=4)
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 def datetime_adapter(obj, request):
55 def datetime_adapter(obj, request):
56 return obj.isoformat()
56 return obj.isoformat()
57
57
58
58
59 def all_permissions_adapter(obj, request):
59 def all_permissions_adapter(obj, request):
60 return '__all_permissions__'
60 return '__all_permissions__'
61
61
62
62
63 json_renderer.add_adapter(datetime.datetime, datetime_adapter)
63 json_renderer.add_adapter(datetime.datetime, datetime_adapter)
64 json_renderer.add_adapter(AllPermissionsList, all_permissions_adapter)
64 json_renderer.add_adapter(AllPermissionsList, all_permissions_adapter)
65
65
66
66
67 def main(global_config, **settings):
67 def main(global_config, **settings):
68 """ This function returns a Pyramid WSGI application.
68 """ This function returns a Pyramid WSGI application.
69 """
69 """
70 auth_tkt_policy = AuthTktAuthenticationPolicy(
70 auth_tkt_policy = AuthTktAuthenticationPolicy(
71 settings['authtkt.secret'],
71 settings['authtkt.secret'],
72 hashalg='sha512',
72 hashalg='sha512',
73 callback=groupfinder,
73 callback=groupfinder,
74 max_age=2592000,
74 max_age=2592000,
75 secure=asbool(settings.get('authtkt.secure', 'false')))
75 secure=asbool(settings.get('authtkt.secure', 'false')))
76 auth_token_policy = AuthTokenAuthenticationPolicy(
76 auth_token_policy = AuthTokenAuthenticationPolicy(
77 callback=groupfinder
77 callback=groupfinder
78 )
78 )
79 authorization_policy = ACLAuthorizationPolicy()
79 authorization_policy = ACLAuthorizationPolicy()
80 authentication_policy = AuthenticationStackPolicy()
80 authentication_policy = AuthenticationStackPolicy()
81 authentication_policy.add_policy('auth_tkt', auth_tkt_policy)
81 authentication_policy.add_policy('auth_tkt', auth_tkt_policy)
82 authentication_policy.add_policy('auth_token', auth_token_policy)
82 authentication_policy.add_policy('auth_token', auth_token_policy)
83 # set crypto key
83 # set crypto key
84 encryption.ENCRYPTION_SECRET = settings.get('encryption_secret')
84 encryption.ENCRYPTION_SECRET = settings.get('encryption_secret')
85 # import this later so encyption key can be monkeypatched
85 # import this later so encyption key can be monkeypatched
86 from appenlight.models import DBSession, register_datastores
86 from appenlight.models import DBSession, register_datastores
87
note

This is a test

88 # registration
89 settings['appenlight.disable_registration'] = asbool(
90 settings.get('appenlight.disable_registration'))
91
87 # update config with cometd info
92 # update config with cometd info
88 settings['cometd_servers'] = {'server': settings['cometd.server'],
93 settings['cometd_servers'] = {'server': settings['cometd.server'],
89 'secret': settings['cometd.secret']}
94 'secret': settings['cometd.secret']}
90
95
91 # Create the Pyramid Configurator.
96 # Create the Pyramid Configurator.
92 settings['_mail_url'] = settings['mailing.app_url']
97 settings['_mail_url'] = settings['mailing.app_url']
93 config = CythonCompatConfigurator(
98 config = CythonCompatConfigurator(
94 settings=settings,
99 settings=settings,
95 authentication_policy=authentication_policy,
100 authentication_policy=authentication_policy,
96 authorization_policy=authorization_policy,
101 authorization_policy=authorization_policy,
97 root_factory='appenlight.security.RootFactory',
102 root_factory='appenlight.security.RootFactory',
98 default_permission='view')
103 default_permission='view')
99 # custom registry variables
104 # custom registry variables
100
105
101 # resource type information
106 # resource type information
102 config.registry.resource_types = ['resource', 'application']
107 config.registry.resource_types = ['resource', 'application']
103 # plugin information
108 # plugin information
104 config.registry.appenlight_plugins = {}
109 config.registry.appenlight_plugins = {}
105
110
106 config.set_default_csrf_options(require_csrf=True, header='X-XSRF-TOKEN')
111 config.set_default_csrf_options(require_csrf=True, header='X-XSRF-TOKEN')
107 config.add_view_deriver('appenlight.predicates.csrf_view',
112 config.add_view_deriver('appenlight.predicates.csrf_view',
108 name='csrf_view')
113 name='csrf_view')
109
114
110 # later, when config is available
115 # later, when config is available
111 dogpile_config = {'url': settings['redis.url'],
116 dogpile_config = {'url': settings['redis.url'],
112 "redis_expiration_time": 86400,
117 "redis_expiration_time": 86400,
113 "redis_distributed_lock": True}
118 "redis_distributed_lock": True}
114 cache_regions.regions = cache_regions.CacheRegions(dogpile_config)
119 cache_regions.regions = cache_regions.CacheRegions(dogpile_config)
115 config.registry.cache_regions = cache_regions.regions
120 config.registry.cache_regions = cache_regions.regions
116 engine = engine_from_config(settings, 'sqlalchemy.',
121 engine = engine_from_config(settings, 'sqlalchemy.',
117 json_serializer=json.dumps)
122 json_serializer=json.dumps)
118 DBSession.configure(bind=engine)
123 DBSession.configure(bind=engine)
119
124
120 # json rederer that serializes datetime
125 # json rederer that serializes datetime
121 config.add_renderer('json', json_renderer)
126 config.add_renderer('json', json_renderer)
122 config.set_request_property('appenlight.lib.request.es_conn', 'es_conn')
127 config.set_request_property('appenlight.lib.request.es_conn', 'es_conn')
123 config.set_request_property('appenlight.lib.request.get_user', 'user',
128 config.set_request_property('appenlight.lib.request.get_user', 'user',
124 reify=True)
129 reify=True)
125 config.set_request_property('appenlight.lib.request.get_csrf_token',
130 config.set_request_property('appenlight.lib.request.get_csrf_token',
126 'csrf_token', reify=True)
131 'csrf_token', reify=True)
127 config.set_request_property('appenlight.lib.request.safe_json_body',
132 config.set_request_property('appenlight.lib.request.safe_json_body',
128 'safe_json_body', reify=True)
133 'safe_json_body', reify=True)
129 config.set_request_property('appenlight.lib.request.unsafe_json_body',
134 config.set_request_property('appenlight.lib.request.unsafe_json_body',
130 'unsafe_json_body', reify=True)
135 'unsafe_json_body', reify=True)
131 config.add_request_method('appenlight.lib.request.add_flash_to_headers',
136 config.add_request_method('appenlight.lib.request.add_flash_to_headers',
132 'add_flash_to_headers')
137 'add_flash_to_headers')
133 config.add_request_method('appenlight.lib.request.get_authomatic',
138 config.add_request_method('appenlight.lib.request.get_authomatic',
134 'authomatic', reify=True)
139 'authomatic', reify=True)
135
140
136 config.include('pyramid_redis_sessions')
141 config.include('pyramid_redis_sessions')
137 config.include('pyramid_tm')
142 config.include('pyramid_tm')
138 config.include('pyramid_jinja2')
143 config.include('pyramid_jinja2')
139 config.include('appenlight_client.ext.pyramid_tween')
144 config.include('appenlight_client.ext.pyramid_tween')
140 config.include('ziggurat_foundations.ext.pyramid.sign_in')
145 config.include('ziggurat_foundations.ext.pyramid.sign_in')
141 es_server_list = aslist(settings['elasticsearch.nodes'])
146 es_server_list = aslist(settings['elasticsearch.nodes'])
142 redis_url = settings['redis.url']
147 redis_url = settings['redis.url']
143 log.warning('Elasticsearch server list: {}'.format(es_server_list))
148 log.warning('Elasticsearch server list: {}'.format(es_server_list))
144 log.warning('Redis server: {}'.format(redis_url))
149 log.warning('Redis server: {}'.format(redis_url))
145 config.registry.es_conn = pyelasticsearch.ElasticSearch(es_server_list)
150 config.registry.es_conn = pyelasticsearch.ElasticSearch(es_server_list)
146 config.registry.redis_conn = redis.StrictRedis.from_url(redis_url)
151 config.registry.redis_conn = redis.StrictRedis.from_url(redis_url)
147
152
148 config.registry.redis_lockmgr = Redlock([settings['redis.redlock.url'], ],
153 config.registry.redis_lockmgr = Redlock([settings['redis.redlock.url'], ],
149 retry_count=0, retry_delay=0)
154 retry_count=0, retry_delay=0)
150 # mailer
155 # mailer
151 config.registry.mailer = Mailer.from_settings(settings)
156 config.registry.mailer = Mailer.from_settings(settings)
152
157
153 # Configure sessions
158 # Configure sessions
154 session_factory = session_factory_from_settings(settings)
159 session_factory = session_factory_from_settings(settings)
155 config.set_session_factory(session_factory)
160 config.set_session_factory(session_factory)
156
161
157 # Configure renderers and event subscribers
162 # Configure renderers and event subscribers
158 config.add_jinja2_extension('jinja2.ext.loopcontrols')
163 config.add_jinja2_extension('jinja2.ext.loopcontrols')
159 config.add_jinja2_search_path('appenlight:templates')
164 config.add_jinja2_search_path('appenlight:templates')
160 # event subscribers
165 # event subscribers
161 config.add_subscriber("appenlight.subscribers.application_created",
166 config.add_subscriber("appenlight.subscribers.application_created",
162 "pyramid.events.ApplicationCreated")
167 "pyramid.events.ApplicationCreated")
163 config.add_subscriber("appenlight.subscribers.add_renderer_globals",
168 config.add_subscriber("appenlight.subscribers.add_renderer_globals",
164 "pyramid.events.BeforeRender")
169 "pyramid.events.BeforeRender")
165 config.add_subscriber('appenlight.subscribers.new_request',
170 config.add_subscriber('appenlight.subscribers.new_request',
166 'pyramid.events.NewRequest')
171 'pyramid.events.NewRequest')
167 config.add_view_predicate('context_type_class',
172 config.add_view_predicate('context_type_class',
168 'appenlight.predicates.contextTypeClass')
173 'appenlight.predicates.contextTypeClass')
169
174
170 register_datastores(es_conn=config.registry.es_conn,
175 register_datastores(es_conn=config.registry.es_conn,
171 redis_conn=config.registry.redis_conn,
176 redis_conn=config.registry.redis_conn,
172 redis_lockmgr=config.registry.redis_lockmgr)
177 redis_lockmgr=config.registry.redis_lockmgr)
173
178
174 # base stuff and scan
179 # base stuff and scan
175
180
176 # need to ensure webassets exists otherwise config.override_asset()
181 # need to ensure webassets exists otherwise config.override_asset()
177 # throws exception
182 # throws exception
178 if not os.path.exists(settings['webassets.dir']):
183 if not os.path.exists(settings['webassets.dir']):
179 os.mkdir(settings['webassets.dir'])
184 os.mkdir(settings['webassets.dir'])
180 config.add_static_view(path='appenlight:webassets',
185 config.add_static_view(path='appenlight:webassets',
181 name='static', cache_max_age=3600)
186 name='static', cache_max_age=3600)
182 config.override_asset(to_override='appenlight:webassets/',
187 config.override_asset(to_override='appenlight:webassets/',
183 override_with=settings['webassets.dir'])
188 override_with=settings['webassets.dir'])
184
189
185 config.include('appenlight.views')
190 config.include('appenlight.views')
186 config.include('appenlight.views.admin')
191 config.include('appenlight.views.admin')
187 config.scan(ignore=['appenlight.migrations', 'appenlight.scripts',
192 config.scan(ignore=['appenlight.migrations', 'appenlight.scripts',
188 'appenlight.tests'])
193 'appenlight.tests'])
189
194
190 config.add_directive('register_appenlight_plugin',
195 config.add_directive('register_appenlight_plugin',
191 register_appenlight_plugin)
196 register_appenlight_plugin)
192
197
193 for entry_point in iter_entry_points(group='appenlight.plugins'):
198 for entry_point in iter_entry_points(group='appenlight.plugins'):
194 plugin = entry_point.load()
199 plugin = entry_point.load()
195 plugin.includeme(config)
200 plugin.includeme(config)
196
201
197 # include other appenlight plugins explictly if needed
202 # include other appenlight plugins explictly if needed
198 includes = aslist(settings.get('appenlight.includes', []))
203 includes = aslist(settings.get('appenlight.includes', []))
199 for inc in includes:
204 for inc in includes:
200 config.include(inc)
205 config.include(inc)
201
206
202 # run this after everything registers in configurator
207 # run this after everything registers in configurator
203
208
204 def pre_commit():
209 def pre_commit():
205 jinja_env = config.get_jinja2_environment()
210 jinja_env = config.get_jinja2_environment()
206 jinja_env.filters['tojson'] = json.dumps
211 jinja_env.filters['tojson'] = json.dumps
207 jinja_env.filters['toJSONUnsafe'] = jinja2_filters.toJSONUnsafe
212 jinja_env.filters['toJSONUnsafe'] = jinja2_filters.toJSONUnsafe
208
213
209 config.action(None, pre_commit, order=PHASE3_CONFIG + 999)
214 config.action(None, pre_commit, order=PHASE3_CONFIG + 999)
210
215
211 def wrap_config_celery():
216 def wrap_config_celery():
212 configure_celery(config.registry)
217 configure_celery(config.registry)
213
218
214 config.action(None, wrap_config_celery, order=PHASE3_CONFIG + 999)
219 config.action(None, wrap_config_celery, order=PHASE3_CONFIG + 999)
215
220
216 app = config.make_wsgi_app()
221 app = config.make_wsgi_app()
217 return app
222 return app
@@ -1,237 +1,241 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
3 # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
4 #
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
7 # You may obtain a copy of the License at
8 #
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
10 #
11 # Unless required by applicable law or agreed to in writing, software
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
15 # limitations under the License.
16
16
17 import datetime
17 import datetime
18 import logging
18 import logging
19 import uuid
19 import uuid
20
20
21 import pyramid.security as security
21 import pyramid.security as security
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.response import Response
25 from pyramid.response import Response
26 from pyramid.security import NO_PERMISSION_REQUIRED
26 from pyramid.security import NO_PERMISSION_REQUIRED
27 from ziggurat_foundations.ext.pyramid.sign_in import ZigguratSignInSuccess
27 from ziggurat_foundations.ext.pyramid.sign_in import ZigguratSignInSuccess
28 from ziggurat_foundations.ext.pyramid.sign_in import ZigguratSignInBadAuth
28 from ziggurat_foundations.ext.pyramid.sign_in import ZigguratSignInBadAuth
29 from ziggurat_foundations.ext.pyramid.sign_in import ZigguratSignOut
29 from ziggurat_foundations.ext.pyramid.sign_in import ZigguratSignOut
30
30
31 from appenlight.lib.social import handle_social_data
31 from appenlight.lib.social import handle_social_data
32 from appenlight.models import DBSession
32 from appenlight.models import DBSession
33 from appenlight.models.user import User
33 from appenlight.models.user import User
34 from appenlight.models.services.user import UserService
34 from appenlight.models.services.user import UserService
35 from appenlight.subscribers import _
35 from appenlight.subscribers import _
36 from appenlight import forms
36 from appenlight import forms
37 from webob.multidict import MultiDict
37 from webob.multidict import MultiDict
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 @view_config(context=ZigguratSignInSuccess, permission=NO_PERMISSION_REQUIRED)
42 @view_config(context=ZigguratSignInSuccess, permission=NO_PERMISSION_REQUIRED)
43 def sign_in(request):
43 def sign_in(request):
44 """
44 """
45 Performs sign in by sending proper user identification headers
45 Performs sign in by sending proper user identification headers
46 Regenerates CSRF token
46 Regenerates CSRF token
47 """
47 """
48 user = request.context.user
48 user = request.context.user
49 if user.status == 1:
49 if user.status == 1:
50 request.session.new_csrf_token()
50 request.session.new_csrf_token()
51 user.last_login_date = datetime.datetime.utcnow()
51 user.last_login_date = datetime.datetime.utcnow()
52 social_data = request.session.get('zigg.social_auth')
52 social_data = request.session.get('zigg.social_auth')
53 if social_data:
53 if social_data:
54 handle_social_data(request, user, social_data)
54 handle_social_data(request, user, social_data)
55 else:
55 else:
56 request.session.flash(_('Account got disabled'))
56 request.session.flash(_('Account got disabled'))
57
57
58 if request.context.came_from != '/':
58 if request.context.came_from != '/':
59 return HTTPFound(location=request.context.came_from,
59 return HTTPFound(location=request.context.came_from,
60 headers=request.context.headers)
60 headers=request.context.headers)
61 else:
61 else:
62 return HTTPFound(location=request.route_url('/'),
62 return HTTPFound(location=request.route_url('/'),
63 headers=request.context.headers)
63 headers=request.context.headers)
64
64
65
65
66 @view_config(context=ZigguratSignInBadAuth, permission=NO_PERMISSION_REQUIRED)
66 @view_config(context=ZigguratSignInBadAuth, permission=NO_PERMISSION_REQUIRED)
67 def bad_auth(request):
67 def bad_auth(request):
68 """
68 """
69 Handles incorrect login flow
69 Handles incorrect login flow
70 """
70 """
71 request.session.flash(_('Incorrect username or password'), 'warning')
71 request.session.flash(_('Incorrect username or password'), 'warning')
72 return HTTPFound(location=request.route_url('register'),
72 return HTTPFound(location=request.route_url('register'),
73 headers=request.context.headers)
73 headers=request.context.headers)
74
74
75
75
76 @view_config(context=ZigguratSignOut, permission=NO_PERMISSION_REQUIRED)
76 @view_config(context=ZigguratSignOut, permission=NO_PERMISSION_REQUIRED)
77 def sign_out(request):
77 def sign_out(request):
78 """
78 """
79 Removes user identification cookie
79 Removes user identification cookie
80 """
80 """
81 return HTTPFound(location=request.route_url('register'),
81 return HTTPFound(location=request.route_url('register'),
82 headers=request.context.headers)
82 headers=request.context.headers)
83
83
84
84
85 @view_config(route_name='lost_password',
85 @view_config(route_name='lost_password',
86 renderer='appenlight:templates/user/lost_password.jinja2',
86 renderer='appenlight:templates/user/lost_password.jinja2',
87 permission=NO_PERMISSION_REQUIRED)
87 permission=NO_PERMISSION_REQUIRED)
88 def lost_password(request):
88 def lost_password(request):
89 """
89 """
90 Presents lost password page - sends password reset link to
90 Presents lost password page - sends password reset link to
91 specified email address.
91 specified email address.
92 This link is valid only for 10 minutes
92 This link is valid only for 10 minutes
93 """
93 """
94 form = forms.LostPasswordForm(request.POST, csrf_context=request)
94 form = forms.LostPasswordForm(request.POST, csrf_context=request)
95 if request.method == 'POST' and form.validate():
95 if request.method == 'POST' and form.validate():
96 user = User.by_email(form.email.data)
96 user = User.by_email(form.email.data)
97 if user:
97 if user:
98 user.regenerate_security_code()
98 user.regenerate_security_code()
99 user.security_code_date = datetime.datetime.utcnow()
99 user.security_code_date = datetime.datetime.utcnow()
100 email_vars = {
100 email_vars = {
101 'user': user,
101 'user': user,
102 'request': request,
102 'request': request,
103 'email_title': "AppEnlight :: New password request"
103 'email_title': "AppEnlight :: New password request"
104 }
104 }
105 UserService.send_email(
105 UserService.send_email(
106 request, recipients=[user.email],
106 request, recipients=[user.email],
107 variables=email_vars,
107 variables=email_vars,
108 template='/email_templates/lost_password.jinja2')
108 template='/email_templates/lost_password.jinja2')
109 msg = 'Password reset email had been sent. ' \
109 msg = 'Password reset email had been sent. ' \
110 'Please check your mailbox for further instructions.'
110 'Please check your mailbox for further instructions.'
111 request.session.flash(_(msg))
111 request.session.flash(_(msg))
112 return HTTPFound(location=request.route_url('lost_password'))
112 return HTTPFound(location=request.route_url('lost_password'))
113 return {"form": form}
113 return {"form": form}
114
114
115
115
116 @view_config(route_name='lost_password_generate',
116 @view_config(route_name='lost_password_generate',
117 permission=NO_PERMISSION_REQUIRED,
117 permission=NO_PERMISSION_REQUIRED,
118 renderer='appenlight:templates/user/lost_password_generate.jinja2')
118 renderer='appenlight:templates/user/lost_password_generate.jinja2')
119 def lost_password_generate(request):
119 def lost_password_generate(request):
120 """
120 """
121 Shows new password form - perform time check and set new password for user
121 Shows new password form - perform time check and set new password for user
122 """
122 """
123 user = User.by_user_name_and_security_code(
123 user = User.by_user_name_and_security_code(
124 request.GET.get('user_name'), request.GET.get('security_code'))
124 request.GET.get('user_name'), request.GET.get('security_code'))
125 if user:
125 if user:
126 delta = datetime.datetime.utcnow() - user.security_code_date
126 delta = datetime.datetime.utcnow() - user.security_code_date
127
127
128 if user and delta.total_seconds() < 600:
128 if user and delta.total_seconds() < 600:
129 form = forms.NewPasswordForm(request.POST, csrf_context=request)
129 form = forms.NewPasswordForm(request.POST, csrf_context=request)
130 if request.method == "POST" and form.validate():
130 if request.method == "POST" and form.validate():
131 user.set_password(form.new_password.data)
131 user.set_password(form.new_password.data)
132 request.session.flash(_('You can sign in with your new password.'))
132 request.session.flash(_('You can sign in with your new password.'))
133 return HTTPFound(location=request.route_url('register'))
133 return HTTPFound(location=request.route_url('register'))
134 else:
134 else:
135 return {"form": form}
135 return {"form": form}
136 else:
136 else:
137 return Response('Security code expired')
137 return Response('Security code expired')
138
138
139
139
140 @view_config(route_name='register',
140 @view_config(route_name='register',
141 renderer='appenlight:templates/user/register.jinja2',
141 renderer='appenlight:templates/user/register.jinja2',
142 permission=NO_PERMISSION_REQUIRED)
142 permission=NO_PERMISSION_REQUIRED)
143 def register(request):
143 def register(request):
144 """
144 """
145 Render register page with form
145 Render register page with form
146 Also handles oAuth flow for registration
146 Also handles oAuth flow for registration
147 """
147 """
148 login_url = request.route_url('ziggurat.routes.sign_in')
148 login_url = request.route_url('ziggurat.routes.sign_in')
149 if request.query_string:
149 if request.query_string:
150 query_string = '?%s' % request.query_string
150 query_string = '?%s' % request.query_string
151 else:
151 else:
152 query_string = ''
152 query_string = ''
153 referrer = '%s%s' % (request.path, query_string)
153 referrer = '%s%s' % (request.path, query_string)
154
154
155 if referrer in [login_url, '/register', '/register?sign_in=1']:
155 if referrer in [login_url, '/register', '/register?sign_in=1']:
156 referrer = '/' # never use the login form itself as came_from
156 referrer = '/' # never use the login form itself as came_from
157 sign_in_form = forms.SignInForm(
157 sign_in_form = forms.SignInForm(
158 came_from=request.params.get('came_from', referrer),
158 came_from=request.params.get('came_from', referrer),
159 csrf_context=request)
159 csrf_context=request)
160
160
161 # populate form from oAuth session data returned by authomatic
161 # populate form from oAuth session data returned by authomatic
162 social_data = request.session.get('zigg.social_auth')
162 social_data = request.session.get('zigg.social_auth')
163 if request.method != 'POST' and social_data:
163 if request.method != 'POST' and social_data:
164 log.debug(social_data)
164 log.debug(social_data)
165 user_name = social_data['user'].get('user_name', '').split('@')[0]
165 user_name = social_data['user'].get('user_name', '').split('@')[0]
166 form_data = {
166 form_data = {
167 'user_name': user_name,
167 'user_name': user_name,
168 'email': social_data['user'].get('email')
168 'email': social_data['user'].get('email')
169 }
169 }
170 form_data['user_password'] = str(uuid.uuid4())
170 form_data['user_password'] = str(uuid.uuid4())
171 form = forms.UserRegisterForm(MultiDict(form_data),
171 form = forms.UserRegisterForm(MultiDict(form_data),
172 csrf_context=request)
172 csrf_context=request)
173 form.user_password.widget.hide_value = False
173 form.user_password.widget.hide_value = False
174 else:
174 else:
175 form = forms.UserRegisterForm(request.POST, csrf_context=request)
175 form = forms.UserRegisterForm(request.POST, csrf_context=request)
176 if request.method == 'POST' and form.validate():
176 if request.method == 'POST' and form.validate():
177 log.info('registering user')
177 log.info('registering user')
178 # insert new user here
178 # insert new user here
179 if request.registry.settings['appenlight.disable_registration']:
180 request.session.flash(_('Registration is currently disabled.'))
181 return HTTPFound(location=request.route_url('/'))
182
179 new_user = User()
183 new_user = User()
180 DBSession.add(new_user)
184 DBSession.add(new_user)
181 form.populate_obj(new_user)
185 form.populate_obj(new_user)
182 new_user.regenerate_security_code()
186 new_user.regenerate_security_code()
183 new_user.status = 1
187 new_user.status = 1
184 new_user.set_password(new_user.user_password)
188 new_user.set_password(new_user.user_password)
185 new_user.registration_ip = request.environ.get('REMOTE_ADDR')
189 new_user.registration_ip = request.environ.get('REMOTE_ADDR')
186
190
187 if social_data:
191 if social_data:
188 handle_social_data(request, new_user, social_data)
192 handle_social_data(request, new_user, social_data)
189
193
190 email_vars = {'user': new_user,
194 email_vars = {'user': new_user,
191 'request': request,
195 'request': request,
192 'email_title': "AppEnlight :: Start information"}
196 'email_title': "AppEnlight :: Start information"}
193 UserService.send_email(
197 UserService.send_email(
194 request, recipients=[new_user.email], variables=email_vars,
198 request, recipients=[new_user.email], variables=email_vars,
195 template='/email_templates/registered.jinja2')
199 template='/email_templates/registered.jinja2')
196 request.session.flash(_('You have successfully registered.'))
200 request.session.flash(_('You have successfully registered.'))
197 DBSession.flush()
201 DBSession.flush()
198 headers = security.remember(request, new_user.id)
202 headers = security.remember(request, new_user.id)
199 return HTTPFound(location=request.route_url('/'),
203 return HTTPFound(location=request.route_url('/'),
200 headers=headers)
204 headers=headers)
201 settings = request.registry.settings
205 settings = request.registry.settings
202 social_plugins = {}
206 social_plugins = {}
203 if settings.get('authomatic.pr.twitter.key', ''):
207 if settings.get('authomatic.pr.twitter.key', ''):
204 social_plugins['twitter'] = True
208 social_plugins['twitter'] = True
205 if settings.get('authomatic.pr.google.key', ''):
209 if settings.get('authomatic.pr.google.key', ''):
206 social_plugins['google'] = True
210 social_plugins['google'] = True
207 if settings.get('authomatic.pr.github.key', ''):
211 if settings.get('authomatic.pr.github.key', ''):
208 social_plugins['github'] = True
212 social_plugins['github'] = True
209 if settings.get('authomatic.pr.bitbucket.key', ''):
213 if settings.get('authomatic.pr.bitbucket.key', ''):
210 social_plugins['bitbucket'] = True
214 social_plugins['bitbucket'] = True
211
215
212 return {
216 return {
213 "form": form,
217 "form": form,
214 "sign_in_form": sign_in_form,
218 "sign_in_form": sign_in_form,
215 "social_plugins": social_plugins
219 "social_plugins": social_plugins
216 }
220 }
217
221
218
222
219 @view_config(route_name='/',
223 @view_config(route_name='/',
220 renderer='appenlight:templates/app.jinja2',
224 renderer='appenlight:templates/app.jinja2',
221 permission=NO_PERMISSION_REQUIRED)
225 permission=NO_PERMISSION_REQUIRED)
222 @view_config(route_name='angular_app_ui',
226 @view_config(route_name='angular_app_ui',
223 renderer='appenlight:templates/app.jinja2',
227 renderer='appenlight:templates/app.jinja2',
224 permission=NO_PERMISSION_REQUIRED)
228 permission=NO_PERMISSION_REQUIRED)
225 @view_config(route_name='angular_app_ui_ix',
229 @view_config(route_name='angular_app_ui_ix',
226 renderer='appenlight:templates/app.jinja2',
230 renderer='appenlight:templates/app.jinja2',
227 permission=NO_PERMISSION_REQUIRED)
231 permission=NO_PERMISSION_REQUIRED)
228 def app_main_index(request):
232 def app_main_index(request):
229 """
233 """
230 Render dashoard/report browser page page along with:
234 Render dashoard/report browser page page along with:
231 - flash messages
235 - flash messages
232 - application list
236 - application list
233 - assigned reports
237 - assigned reports
234 - latest events
238 - latest events
235 (those last two come from subscribers.py that sets global renderer variables)
239 (those last two come from subscribers.py that sets global renderer variables)
236 """
240 """
237 return {}
241 return {}
@@ -1,674 +1,678 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
3 # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
4 #
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
7 # You may obtain a copy of the License at
8 #
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
10 #
11 # Unless required by applicable law or agreed to in writing, software
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
15 # limitations under the License.
16
16
17 import colander
17 import colander
18 import datetime
18 import datetime
19 import json
19 import json
20 import logging
20 import logging
21 import uuid
21 import uuid
22 import pyramid.security as security
22 import pyramid.security as security
23 import appenlight.lib.helpers as h
23 import appenlight.lib.helpers as h
24
24
25 from authomatic.adapters import WebObAdapter
25 from authomatic.adapters import WebObAdapter
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.httpexceptions import HTTPFound, HTTPUnprocessableEntity
27 from pyramid.httpexceptions import HTTPFound, HTTPUnprocessableEntity
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest
29 from pyramid.security import NO_PERMISSION_REQUIRED
29 from pyramid.security import NO_PERMISSION_REQUIRED
30 from ziggurat_foundations.models.services.external_identity import \
30 from ziggurat_foundations.models.services.external_identity import \
31 ExternalIdentityService
31 ExternalIdentityService
32
32
33 from appenlight.lib import generate_random_string
33 from appenlight.lib import generate_random_string
34 from appenlight.lib.social import handle_social_data
34 from appenlight.lib.social import handle_social_data
35 from appenlight.lib.utils import channelstream_request, add_cors_headers, \
35 from appenlight.lib.utils import channelstream_request, add_cors_headers, \
36 permission_tuple_to_dict
36 permission_tuple_to_dict
37 from appenlight.models import DBSession
37 from appenlight.models import DBSession
38 from appenlight.models.alert_channels.email import EmailAlertChannel
38 from appenlight.models.alert_channels.email import EmailAlertChannel
39 from appenlight.models.alert_channel_action import AlertChannelAction
39 from appenlight.models.alert_channel_action import AlertChannelAction
40 from appenlight.models.services.alert_channel import AlertChannelService
40 from appenlight.models.services.alert_channel import AlertChannelService
41 from appenlight.models.services.alert_channel_action import \
41 from appenlight.models.services.alert_channel_action import \
42 AlertChannelActionService
42 AlertChannelActionService
43 from appenlight.models.auth_token import AuthToken
43 from appenlight.models.auth_token import AuthToken
44 from appenlight.models.report import REPORT_TYPE_MATRIX
44 from appenlight.models.report import REPORT_TYPE_MATRIX
45 from appenlight.models.user import User
45 from appenlight.models.user import User
46 from appenlight.models.services.user import UserService
46 from appenlight.models.services.user import UserService
47 from appenlight.subscribers import _
47 from appenlight.subscribers import _
48 from appenlight.validators import build_rule_schema
48 from appenlight.validators import build_rule_schema
49 from appenlight import forms
49 from appenlight import forms
50 from webob.multidict import MultiDict
50 from webob.multidict import MultiDict
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 @view_config(route_name='users_no_id', renderer='json',
55 @view_config(route_name='users_no_id', renderer='json',
56 request_method="GET", permission='root_administration')
56 request_method="GET", permission='root_administration')
57 def users_list(request):
57 def users_list(request):
58 """
58 """
59 Returns users list
59 Returns users list
60 """
60 """
61 props = ['user_name', 'id', 'first_name', 'last_name', 'email',
61 props = ['user_name', 'id', 'first_name', 'last_name', 'email',
62 'last_login_date', 'status']
62 'last_login_date', 'status']
63 users = UserService.all()
63 users = UserService.all()
64 users_dicts = []
64 users_dicts = []
65 for user in users:
65 for user in users:
66 u_dict = user.get_dict(include_keys=props)
66 u_dict = user.get_dict(include_keys=props)
67 u_dict['gravatar_url'] = user.gravatar_url(s=20)
67 u_dict['gravatar_url'] = user.gravatar_url(s=20)
68 users_dicts.append(u_dict)
68 users_dicts.append(u_dict)
69 return users_dicts
69 return users_dicts
70
70
71
71
72 @view_config(route_name='users_no_id', renderer='json',
72 @view_config(route_name='users_no_id', renderer='json',
73 request_method="POST", permission='root_administration')
73 request_method="POST", permission='root_administration')
74 def users_create(request):
74 def users_create(request):
75 """
75 """
76 Returns users list
76 Returns users list
77 """
77 """
78 form = forms.UserCreateForm(MultiDict(request.safe_json_body or {}),
78 form = forms.UserCreateForm(MultiDict(request.safe_json_body or {}),
79 csrf_context=request)
79 csrf_context=request)
80 if form.validate():
80 if form.validate():
81 log.info('registering user')
81 log.info('registering user')
82 # probably not needed in the future since this requires root anyways
83 # lets keep this here in case we lower view permission in the future
84 # if request.registry.settings['appenlight.disable_registration']:
85 # return HTTPUnprocessableEntity(body={'error': 'Registration is currently disabled.'})
82 user = User()
86 user = User()
83 # insert new user here
87 # insert new user here
84 DBSession.add(user)
88 DBSession.add(user)
85 form.populate_obj(user)
89 form.populate_obj(user)
86 user.regenerate_security_code()
90 user.regenerate_security_code()
87 user.set_password(user.user_password)
91 user.set_password(user.user_password)
88 user.status = 1 if form.status.data else 0
92 user.status = 1 if form.status.data else 0
89 request.session.flash(_('User created'))
93 request.session.flash(_('User created'))
90 DBSession.flush()
94 DBSession.flush()
91 return user.get_dict(exclude_keys=['security_code_date', 'notes',
95 return user.get_dict(exclude_keys=['security_code_date', 'notes',
92 'security_code', 'user_password'])
96 'security_code', 'user_password'])
93 else:
97 else:
94 return HTTPUnprocessableEntity(body=form.errors_json)
98 return HTTPUnprocessableEntity(body=form.errors_json)
95
99
96
100
97 @view_config(route_name='users', renderer='json',
101 @view_config(route_name='users', renderer='json',
98 request_method="GET", permission='root_administration')
102 request_method="GET", permission='root_administration')
99 @view_config(route_name='users', renderer='json',
103 @view_config(route_name='users', renderer='json',
100 request_method="PATCH", permission='root_administration')
104 request_method="PATCH", permission='root_administration')
101 def users_update(request):
105 def users_update(request):
102 """
106 """
103 Updates user object
107 Updates user object
104 """
108 """
105 user = User.by_id(request.matchdict.get('user_id'))
109 user = User.by_id(request.matchdict.get('user_id'))
106 if not user:
110 if not user:
107 return HTTPNotFound()
111 return HTTPNotFound()
108 post_data = request.safe_json_body or {}
112 post_data = request.safe_json_body or {}
109 if request.method == 'PATCH':
113 if request.method == 'PATCH':
110 form = forms.UserUpdateForm(MultiDict(post_data),
114 form = forms.UserUpdateForm(MultiDict(post_data),
111 csrf_context=request)
115 csrf_context=request)
112 if form.validate():
116 if form.validate():
113 form.populate_obj(user, ignore_none=True)
117 form.populate_obj(user, ignore_none=True)
114 if form.user_password.data:
118 if form.user_password.data:
115 user.set_password(user.user_password)
119 user.set_password(user.user_password)
116 if form.status.data:
120 if form.status.data:
117 user.status = 1
121 user.status = 1
118 else:
122 else:
119 user.status = 0
123 user.status = 0
120 else:
124 else:
121 return HTTPUnprocessableEntity(body=form.errors_json)
125 return HTTPUnprocessableEntity(body=form.errors_json)
122 return user.get_dict(exclude_keys=['security_code_date', 'notes',
126 return user.get_dict(exclude_keys=['security_code_date', 'notes',
123 'security_code', 'user_password'])
127 'security_code', 'user_password'])
124
128
125
129
126 @view_config(route_name='users_property',
130 @view_config(route_name='users_property',
127 match_param='key=resource_permissions',
131 match_param='key=resource_permissions',
128 renderer='json', permission='authenticated')
132 renderer='json', permission='authenticated')
129 def users_resource_permissions_list(request):
133 def users_resource_permissions_list(request):
130 """
134 """
131 Get list of permissions assigned to specific resources
135 Get list of permissions assigned to specific resources
132 """
136 """
133 user = User.by_id(request.matchdict.get('user_id'))
137 user = User.by_id(request.matchdict.get('user_id'))
134 if not user:
138 if not user:
135 return HTTPNotFound()
139 return HTTPNotFound()
136 return [permission_tuple_to_dict(perm) for perm in
140 return [permission_tuple_to_dict(perm) for perm in
137 user.resources_with_possible_perms()]
141 user.resources_with_possible_perms()]
138
142
139
143
140 @view_config(route_name='users', renderer='json',
144 @view_config(route_name='users', renderer='json',
141 request_method="DELETE", permission='root_administration')
145 request_method="DELETE", permission='root_administration')
142 def users_DELETE(request):
146 def users_DELETE(request):
143 """
147 """
144 Removes a user permanently from db - makes a check to see if after the
148 Removes a user permanently from db - makes a check to see if after the
145 operation there will be at least one admin left
149 operation there will be at least one admin left
146 """
150 """
147 msg = _('There needs to be at least one administrator in the system')
151 msg = _('There needs to be at least one administrator in the system')
148 user = User.by_id(request.matchdict.get('user_id'))
152 user = User.by_id(request.matchdict.get('user_id'))
149 if user:
153 if user:
150 users = User.users_for_perms(['root_administration']).all()
154 users = User.users_for_perms(['root_administration']).all()
151 if len(users) < 2 and user.id == users[0].id:
155 if len(users) < 2 and user.id == users[0].id:
152 request.session.flash(msg, 'warning')
156 request.session.flash(msg, 'warning')
153 else:
157 else:
154 DBSession.delete(user)
158 DBSession.delete(user)
155 request.session.flash(_('User removed'))
159 request.session.flash(_('User removed'))
156 return True
160 return True
157 request.response.status = 422
161 request.response.status = 422
158 return False
162 return False
159
163
160
164
161 @view_config(route_name='users_self', renderer='json',
165 @view_config(route_name='users_self', renderer='json',
162 request_method="GET", permission='authenticated')
166 request_method="GET", permission='authenticated')
163 @view_config(route_name='users_self', renderer='json',
167 @view_config(route_name='users_self', renderer='json',
164 request_method="PATCH", permission='authenticated')
168 request_method="PATCH", permission='authenticated')
165 def users_self(request):
169 def users_self(request):
166 """
170 """
167 Updates user personal information
171 Updates user personal information
168 """
172 """
169
173
170 if request.method == 'PATCH':
174 if request.method == 'PATCH':
171 form = forms.gen_user_profile_form()(
175 form = forms.gen_user_profile_form()(
172 MultiDict(request.unsafe_json_body),
176 MultiDict(request.unsafe_json_body),
173 csrf_context=request)
177 csrf_context=request)
174 if form.validate():
178 if form.validate():
175 form.populate_obj(request.user)
179 form.populate_obj(request.user)
176 request.session.flash(_('Your profile got updated.'))
180 request.session.flash(_('Your profile got updated.'))
177 else:
181 else:
178 return HTTPUnprocessableEntity(body=form.errors_json)
182 return HTTPUnprocessableEntity(body=form.errors_json)
179 return request.user.get_dict(
183 return request.user.get_dict(
180 exclude_keys=['security_code_date', 'notes', 'security_code',
184 exclude_keys=['security_code_date', 'notes', 'security_code',
181 'user_password'],
185 'user_password'],
182 extended_info=True)
186 extended_info=True)
183
187
184
188
185 @view_config(route_name='users_self_property',
189 @view_config(route_name='users_self_property',
186 match_param='key=external_identities', renderer='json',
190 match_param='key=external_identities', renderer='json',
187 request_method='GET', permission='authenticated')
191 request_method='GET', permission='authenticated')
188 def users_external_identies(request):
192 def users_external_identies(request):
189 user = request.user
193 user = request.user
190 identities = [{'provider': ident.provider_name,
194 identities = [{'provider': ident.provider_name,
191 'id': ident.external_user_name} for ident
195 'id': ident.external_user_name} for ident
192 in user.external_identities.all()]
196 in user.external_identities.all()]
193 return identities
197 return identities
194
198
195
199
196 @view_config(route_name='users_self_property',
200 @view_config(route_name='users_self_property',
197 match_param='key=external_identities', renderer='json',
201 match_param='key=external_identities', renderer='json',
198 request_method='DELETE', permission='authenticated')
202 request_method='DELETE', permission='authenticated')
199 def users_external_identies_DELETE(request):
203 def users_external_identies_DELETE(request):
200 """
204 """
201 Unbinds external identities(google,twitter etc.) from user account
205 Unbinds external identities(google,twitter etc.) from user account
202 """
206 """
203 user = request.user
207 user = request.user
204 for identity in user.external_identities.all():
208 for identity in user.external_identities.all():
205 log.info('found identity %s' % identity)
209 log.info('found identity %s' % identity)
206 if (identity.provider_name == request.params.get('provider') and
210 if (identity.provider_name == request.params.get('provider') and
207 identity.external_user_name == request.params.get('id')):
211 identity.external_user_name == request.params.get('id')):
208 log.info('remove identity %s' % identity)
212 log.info('remove identity %s' % identity)
209 DBSession.delete(identity)
213 DBSession.delete(identity)
210 return True
214 return True
211 return False
215 return False
212
216
213
217
214 @view_config(route_name='users_self_property',
218 @view_config(route_name='users_self_property',
215 match_param='key=password', renderer='json',
219 match_param='key=password', renderer='json',
216 request_method='PATCH', permission='authenticated')
220 request_method='PATCH', permission='authenticated')
217 def users_password(request):
221 def users_password(request):
218 """
222 """
219 Sets new password for user account
223 Sets new password for user account
220 """
224 """
221 user = request.user
225 user = request.user
222 form = forms.ChangePasswordForm(MultiDict(request.unsafe_json_body),
226 form = forms.ChangePasswordForm(MultiDict(request.unsafe_json_body),
223 csrf_context=request)
227 csrf_context=request)
224 form.old_password.user = user
228 form.old_password.user = user
225 if form.validate():
229 if form.validate():
226 user.regenerate_security_code()
230 user.regenerate_security_code()
227 user.set_password(form.new_password.data)
231 user.set_password(form.new_password.data)
228 msg = 'Your password got updated. ' \
232 msg = 'Your password got updated. ' \
229 'Next time log in with your new credentials.'
233 'Next time log in with your new credentials.'
230 request.session.flash(_(msg))
234 request.session.flash(_(msg))
231 return True
235 return True
232 else:
236 else:
233 return HTTPUnprocessableEntity(body=form.errors_json)
237 return HTTPUnprocessableEntity(body=form.errors_json)
234 return False
238 return False
235
239
236
240
237 @view_config(route_name='users_self_property', match_param='key=websocket',
241 @view_config(route_name='users_self_property', match_param='key=websocket',
238 renderer='json', permission='authenticated')
242 renderer='json', permission='authenticated')
239 def users_websocket(request):
243 def users_websocket(request):
240 """
244 """
241 Handle authorization of users trying to connect
245 Handle authorization of users trying to connect
242 """
246 """
243 # handle preflight request
247 # handle preflight request
244 user = request.user
248 user = request.user
245 if request.method == 'OPTIONS':
249 if request.method == 'OPTIONS':
246 res = request.response.body('OK')
250 res = request.response.body('OK')
247 add_cors_headers(res)
251 add_cors_headers(res)
248 return res
252 return res
249 applications = user.resources_with_perms(
253 applications = user.resources_with_perms(
250 ['view'], resource_types=['application'])
254 ['view'], resource_types=['application'])
251 channels = ['app_%s' % app.resource_id for app in applications]
255 channels = ['app_%s' % app.resource_id for app in applications]
252 payload = {"username": user.user_name,
256 payload = {"username": user.user_name,
253 "conn_id": str(uuid.uuid4()),
257 "conn_id": str(uuid.uuid4()),
254 "channels": channels
258 "channels": channels
255 }
259 }
256 settings = request.registry.settings
260 settings = request.registry.settings
257 response = channelstream_request(
261 response = channelstream_request(
258 settings['cometd.secret'], '/connect', payload,
262 settings['cometd.secret'], '/connect', payload,
259 servers=[request.registry.settings['cometd_servers']],
263 servers=[request.registry.settings['cometd_servers']],
260 throw_exceptions=True)
264 throw_exceptions=True)
261 return payload
265 return payload
262
266
263
267
264 @view_config(route_name='users_self_property', request_method="GET",
268 @view_config(route_name='users_self_property', request_method="GET",
265 match_param='key=alert_channels', renderer='json',
269 match_param='key=alert_channels', renderer='json',
266 permission='authenticated')
270 permission='authenticated')
267 def alert_channels(request):
271 def alert_channels(request):
268 """
272 """
269 Lists all available alert channels
273 Lists all available alert channels
270 """
274 """
271 user = request.user
275 user = request.user
272 return [c.get_dict(extended_info=True) for c in user.alert_channels]
276 return [c.get_dict(extended_info=True) for c in user.alert_channels]
273
277
274
278
275 @view_config(route_name='users_self_property', match_param='key=alert_actions',
279 @view_config(route_name='users_self_property', match_param='key=alert_actions',
276 request_method="GET", renderer='json', permission='authenticated')
280 request_method="GET", renderer='json', permission='authenticated')
277 def alert_actions(request):
281 def alert_actions(request):
278 """
282 """
279 Lists all available alert channels
283 Lists all available alert channels
280 """
284 """
281 user = request.user
285 user = request.user
282 return [r.get_dict(extended_info=True) for r in user.alert_actions]
286 return [r.get_dict(extended_info=True) for r in user.alert_actions]
283
287
284
288
285 @view_config(route_name='users_self_property', renderer='json',
289 @view_config(route_name='users_self_property', renderer='json',
286 match_param='key=alert_channels_rules', request_method='POST',
290 match_param='key=alert_channels_rules', request_method='POST',
287 permission='authenticated')
291 permission='authenticated')
288 def alert_channels_rule_POST(request):
292 def alert_channels_rule_POST(request):
289 """
293 """
290 Creates new notification rule for specific alert channel
294 Creates new notification rule for specific alert channel
291 """
295 """
292 user = request.user
296 user = request.user
293 alert_action = AlertChannelAction(owner_id=request.user.id,
297 alert_action = AlertChannelAction(owner_id=request.user.id,
294 type='report')
298 type='report')
295 DBSession.add(alert_action)
299 DBSession.add(alert_action)
296 DBSession.flush()
300 DBSession.flush()
297 return alert_action.get_dict()
301 return alert_action.get_dict()
298
302
299
303
300 @view_config(route_name='users_self_property', permission='authenticated',
304 @view_config(route_name='users_self_property', permission='authenticated',
301 match_param='key=alert_channels_rules',
305 match_param='key=alert_channels_rules',
302 renderer='json', request_method='DELETE')
306 renderer='json', request_method='DELETE')
303 def alert_channels_rule_DELETE(request):
307 def alert_channels_rule_DELETE(request):
304 """
308 """
305 Removes specific alert channel rule
309 Removes specific alert channel rule
306 """
310 """
307 user = request.user
311 user = request.user
308 rule_action = AlertChannelActionService.by_owner_id_and_pkey(
312 rule_action = AlertChannelActionService.by_owner_id_and_pkey(
309 user.id,
313 user.id,
310 request.GET.get('pkey'))
314 request.GET.get('pkey'))
311 if rule_action:
315 if rule_action:
312 DBSession.delete(rule_action)
316 DBSession.delete(rule_action)
313 return True
317 return True
314 return HTTPNotFound()
318 return HTTPNotFound()
315
319
316
320
317 @view_config(route_name='users_self_property', permission='authenticated',
321 @view_config(route_name='users_self_property', permission='authenticated',
318 match_param='key=alert_channels_rules',
322 match_param='key=alert_channels_rules',
319 renderer='json', request_method='PATCH')
323 renderer='json', request_method='PATCH')
320 def alert_channels_rule_PATCH(request):
324 def alert_channels_rule_PATCH(request):
321 """
325 """
322 Removes specific alert channel rule
326 Removes specific alert channel rule
323 """
327 """
324 user = request.user
328 user = request.user
325 json_body = request.unsafe_json_body
329 json_body = request.unsafe_json_body
326
330
327 schema = build_rule_schema(json_body['rule'], REPORT_TYPE_MATRIX)
331 schema = build_rule_schema(json_body['rule'], REPORT_TYPE_MATRIX)
328 try:
332 try:
329 schema.deserialize(json_body['rule'])
333 schema.deserialize(json_body['rule'])
330 except colander.Invalid as exc:
334 except colander.Invalid as exc:
331 return HTTPUnprocessableEntity(body=json.dumps(exc.asdict()))
335 return HTTPUnprocessableEntity(body=json.dumps(exc.asdict()))
332
336
333 rule_action = AlertChannelActionService.by_owner_id_and_pkey(
337 rule_action = AlertChannelActionService.by_owner_id_and_pkey(
334 user.id,
338 user.id,
335 request.GET.get('pkey'))
339 request.GET.get('pkey'))
336
340
337 if rule_action:
341 if rule_action:
338 rule_action.rule = json_body['rule']
342 rule_action.rule = json_body['rule']
339 rule_action.resource_id = json_body['resource_id']
343 rule_action.resource_id = json_body['resource_id']
340 rule_action.action = json_body['action']
344 rule_action.action = json_body['action']
341 return rule_action.get_dict()
345 return rule_action.get_dict()
342 return HTTPNotFound()
346 return HTTPNotFound()
343
347
344
348
345 @view_config(route_name='users_self_property', permission='authenticated',
349 @view_config(route_name='users_self_property', permission='authenticated',
346 match_param='key=alert_channels',
350 match_param='key=alert_channels',
347 renderer='json', request_method='PATCH')
351 renderer='json', request_method='PATCH')
348 def alert_channels_PATCH(request):
352 def alert_channels_PATCH(request):
349 user = request.user
353 user = request.user
350 channel_name = request.GET.get('channel_name')
354 channel_name = request.GET.get('channel_name')
351 channel_value = request.GET.get('channel_value')
355 channel_value = request.GET.get('channel_value')
352 # iterate over channels
356 # iterate over channels
353 channel = None
357 channel = None
354 for channel in user.alert_channels:
358 for channel in user.alert_channels:
355 if (channel.channel_name == channel_name and
359 if (channel.channel_name == channel_name and
356 channel.channel_value == channel_value):
360 channel.channel_value == channel_value):
357 break
361 break
358 if not channel:
362 if not channel:
359 return HTTPNotFound()
363 return HTTPNotFound()
360
364
361 allowed_keys = ['daily_digest', 'send_alerts']
365 allowed_keys = ['daily_digest', 'send_alerts']
362 for k, v in request.unsafe_json_body.items():
366 for k, v in request.unsafe_json_body.items():
363 if k in allowed_keys:
367 if k in allowed_keys:
364 setattr(channel, k, v)
368 setattr(channel, k, v)
365 else:
369 else:
366 return HTTPBadRequest()
370 return HTTPBadRequest()
367 return channel.get_dict()
371 return channel.get_dict()
368
372
369
373
370 @view_config(route_name='users_self_property', permission='authenticated',
374 @view_config(route_name='users_self_property', permission='authenticated',
371 match_param='key=alert_channels',
375 match_param='key=alert_channels',
372 request_method="POST", renderer='json')
376 request_method="POST", renderer='json')
373 def alert_channels_POST(request):
377 def alert_channels_POST(request):
374 """
378 """
375 Creates a new email alert channel for user, sends a validation email
379 Creates a new email alert channel for user, sends a validation email
376 """
380 """
377 user = request.user
381 user = request.user
378 form = forms.EmailChannelCreateForm(MultiDict(request.unsafe_json_body),
382 form = forms.EmailChannelCreateForm(MultiDict(request.unsafe_json_body),
379 csrf_context=request)
383 csrf_context=request)
380 if not form.validate():
384 if not form.validate():
381 return HTTPUnprocessableEntity(body=form.errors_json)
385 return HTTPUnprocessableEntity(body=form.errors_json)
382
386
383 email = form.email.data.strip()
387 email = form.email.data.strip()
384 channel = EmailAlertChannel()
388 channel = EmailAlertChannel()
385 channel.channel_name = 'email'
389 channel.channel_name = 'email'
386 channel.channel_value = email
390 channel.channel_value = email
387 security_code = generate_random_string(10)
391 security_code = generate_random_string(10)
388 channel.channel_json_conf = {'security_code': security_code}
392 channel.channel_json_conf = {'security_code': security_code}
389 user.alert_channels.append(channel)
393 user.alert_channels.append(channel)
390
394
391 email_vars = {'user': user,
395 email_vars = {'user': user,
392 'email': email,
396 'email': email,
393 'request': request,
397 'request': request,
394 'security_code': security_code,
398 'security_code': security_code,
395 'email_title': "AppEnlight :: "
399 'email_title': "AppEnlight :: "
396 "Please authorize your email"}
400 "Please authorize your email"}
397
401
398 UserService.send_email(request, recipients=[email],
402 UserService.send_email(request, recipients=[email],
399 variables=email_vars,
403 variables=email_vars,
400 template='/email_templates/authorize_email.jinja2')
404 template='/email_templates/authorize_email.jinja2')
401 request.session.flash(_('Your alert channel was '
405 request.session.flash(_('Your alert channel was '
402 'added to the system.'))
406 'added to the system.'))
403 request.session.flash(
407 request.session.flash(
404 _('You need to authorize your email channel, a message was '
408 _('You need to authorize your email channel, a message was '
405 'sent containing necessary information.'),
409 'sent containing necessary information.'),
406 'warning')
410 'warning')
407 DBSession.flush()
411 DBSession.flush()
408 channel.get_dict()
412 channel.get_dict()
409
413
410
414
411 @view_config(route_name='section_view',
415 @view_config(route_name='section_view',
412 match_param=['section=user_section',
416 match_param=['section=user_section',
413 'view=alert_channels_authorize'],
417 'view=alert_channels_authorize'],
414 renderer='string', permission='authenticated')
418 renderer='string', permission='authenticated')
415 def alert_channels_authorize(request):
419 def alert_channels_authorize(request):
416 """
420 """
417 Performs alert channel authorization based on auth code sent in email
421 Performs alert channel authorization based on auth code sent in email
418 """
422 """
419 user = request.user
423 user = request.user
420 for channel in user.alert_channels:
424 for channel in user.alert_channels:
421 security_code = request.params.get('security_code', '')
425 security_code = request.params.get('security_code', '')
422 if channel.channel_json_conf['security_code'] == security_code:
426 if channel.channel_json_conf['security_code'] == security_code:
423 channel.channel_validated = True
427 channel.channel_validated = True
424 request.session.flash(_('Your email was authorized.'))
428 request.session.flash(_('Your email was authorized.'))
425 return HTTPFound(location=request.route_url('/'))
429 return HTTPFound(location=request.route_url('/'))
426
430
427
431
428 @view_config(route_name='users_self_property', request_method="DELETE",
432 @view_config(route_name='users_self_property', request_method="DELETE",
429 match_param='key=alert_channels', renderer='json',
433 match_param='key=alert_channels', renderer='json',
430 permission='authenticated')
434 permission='authenticated')
431 def alert_channel_DELETE(request):
435 def alert_channel_DELETE(request):
432 """
436 """
433 Removes alert channel from users channel
437 Removes alert channel from users channel
434 """
438 """
435 user = request.user
439 user = request.user
436 channel = None
440 channel = None
437 for chan in user.alert_channels:
441 for chan in user.alert_channels:
438 if (chan.channel_name == request.params.get('channel_name') and
442 if (chan.channel_name == request.params.get('channel_name') and
439 chan.channel_value == request.params.get('channel_value')):
443 chan.channel_value == request.params.get('channel_value')):
440 channel = chan
444 channel = chan
441 break
445 break
442 if channel:
446 if channel:
443 user.alert_channels.remove(channel)
447 user.alert_channels.remove(channel)
444 request.session.flash(_('Your channel was removed.'))
448 request.session.flash(_('Your channel was removed.'))
445 return True
449 return True
446 return False
450 return False
447
451
448
452
449 @view_config(route_name='users_self_property', permission='authenticated',
453 @view_config(route_name='users_self_property', permission='authenticated',
450 match_param='key=alert_channels_actions_binds',
454 match_param='key=alert_channels_actions_binds',
451 renderer='json', request_method="POST")
455 renderer='json', request_method="POST")
452 def alert_channels_actions_binds_POST(request):
456 def alert_channels_actions_binds_POST(request):
453 """
457 """
454 Adds alert action to users channels
458 Adds alert action to users channels
455 """
459 """
456 user = request.user
460 user = request.user
457 json_body = request.unsafe_json_body
461 json_body = request.unsafe_json_body
458 channel = AlertChannelService.by_owner_id_and_pkey(
462 channel = AlertChannelService.by_owner_id_and_pkey(
459 user.id,
463 user.id,
460 json_body.get('channel_pkey'))
464 json_body.get('channel_pkey'))
461
465
462 rule_action = AlertChannelActionService.by_owner_id_and_pkey(
466 rule_action = AlertChannelActionService.by_owner_id_and_pkey(
463 user.id,
467 user.id,
464 json_body.get('action_pkey'))
468 json_body.get('action_pkey'))
465
469
466 if channel and rule_action:
470 if channel and rule_action:
467 if channel.pkey not in [c.pkey for c in rule_action.channels]:
471 if channel.pkey not in [c.pkey for c in rule_action.channels]:
468 rule_action.channels.append(channel)
472 rule_action.channels.append(channel)
469 return rule_action.get_dict(extended_info=True)
473 return rule_action.get_dict(extended_info=True)
470 return HTTPUnprocessableEntity()
474 return HTTPUnprocessableEntity()
471
475
472
476
473 @view_config(route_name='users_self_property', request_method="DELETE",
477 @view_config(route_name='users_self_property', request_method="DELETE",
474 match_param='key=alert_channels_actions_binds',
478 match_param='key=alert_channels_actions_binds',
475 renderer='json', permission='authenticated')
479 renderer='json', permission='authenticated')
476 def alert_channels_actions_binds_DELETE(request):
480 def alert_channels_actions_binds_DELETE(request):
477 """
481 """
478 Removes alert action from users channels
482 Removes alert action from users channels
479 """
483 """
480 user = request.user
484 user = request.user
481 channel = AlertChannelService.by_owner_id_and_pkey(
485 channel = AlertChannelService.by_owner_id_and_pkey(
482 user.id,
486 user.id,
483 request.GET.get('channel_pkey'))
487 request.GET.get('channel_pkey'))
484
488
485 rule_action = AlertChannelActionService.by_owner_id_and_pkey(
489 rule_action = AlertChannelActionService.by_owner_id_and_pkey(
486 user.id,
490 user.id,
487 request.GET.get('action_pkey'))
491 request.GET.get('action_pkey'))
488
492
489 if channel and rule_action:
493 if channel and rule_action:
490 if channel.pkey in [c.pkey for c in rule_action.channels]:
494 if channel.pkey in [c.pkey for c in rule_action.channels]:
491 rule_action.channels.remove(channel)
495 rule_action.channels.remove(channel)
492 return rule_action.get_dict(extended_info=True)
496 return rule_action.get_dict(extended_info=True)
493 return HTTPUnprocessableEntity()
497 return HTTPUnprocessableEntity()
494
498
495
499
496 @view_config(route_name='social_auth_abort',
500 @view_config(route_name='social_auth_abort',
497 renderer='string', permission=NO_PERMISSION_REQUIRED)
501 renderer='string', permission=NO_PERMISSION_REQUIRED)
498 def oauth_abort(request):
502 def oauth_abort(request):
499 """
503 """
500 Handles problems with authorization via velruse
504 Handles problems with authorization via velruse
501 """
505 """
502
506
503
507
504 @view_config(route_name='social_auth', permission=NO_PERMISSION_REQUIRED)
508 @view_config(route_name='social_auth', permission=NO_PERMISSION_REQUIRED)
505 def social_auth(request):
509 def social_auth(request):
506 # Get the internal provider name URL variable.
510 # Get the internal provider name URL variable.
507 provider_name = request.matchdict.get('provider')
511 provider_name = request.matchdict.get('provider')
508
512
509 # Start the login procedure.
513 # Start the login procedure.
510 adapter = WebObAdapter(request, request.response)
514 adapter = WebObAdapter(request, request.response)
511 result = request.authomatic.login(adapter, provider_name)
515 result = request.authomatic.login(adapter, provider_name)
512 if result:
516 if result:
513 if result.error:
517 if result.error:
514 return handle_auth_error(request, result)
518 return handle_auth_error(request, result)
515 elif result.user:
519 elif result.user:
516 return handle_auth_success(request, result)
520 return handle_auth_success(request, result)
517 return request.response
521 return request.response
518
522
519
523
520 def handle_auth_error(request, result):
524 def handle_auth_error(request, result):
521 # Login procedure finished with an error.
525 # Login procedure finished with an error.
522 request.session.pop('zigg.social_auth', None)
526 request.session.pop('zigg.social_auth', None)
523 request.session.flash(_('Something went wrong when we tried to '
527 request.session.flash(_('Something went wrong when we tried to '
524 'authorize you via external provider. '
528 'authorize you via external provider. '
525 'Please try again.'), 'warning')
529 'Please try again.'), 'warning')
526
530
527 return HTTPFound(location=request.route_url('/'))
531 return HTTPFound(location=request.route_url('/'))
528
532
529
533
530 def handle_auth_success(request, result):
534 def handle_auth_success(request, result):
531 # Hooray, we have the user!
535 # Hooray, we have the user!
532 # OAuth 2.0 and OAuth 1.0a provide only limited user data on login,
536 # OAuth 2.0 and OAuth 1.0a provide only limited user data on login,
533 # We need to update the user to get more info.
537 # We need to update the user to get more info.
534 if result.user:
538 if result.user:
535 result.user.update()
539 result.user.update()
536
540
537 social_data = {
541 social_data = {
538 'user': {'data': result.user.data},
542 'user': {'data': result.user.data},
539 'credentials': result.user.credentials
543 'credentials': result.user.credentials
540 }
544 }
541 # normalize data
545 # normalize data
542 social_data['user']['id'] = result.user.id
546 social_data['user']['id'] = result.user.id
543 user_name = result.user.username or ''
547 user_name = result.user.username or ''
544 # use email name as username for google
548 # use email name as username for google
545 if (social_data['credentials'].provider_name == 'google' and
549 if (social_data['credentials'].provider_name == 'google' and
546 result.user.email):
550 result.user.email):
547 user_name = result.user.email
551 user_name = result.user.email
548 social_data['user']['user_name'] = user_name
552 social_data['user']['user_name'] = user_name
549 social_data['user']['email'] = result.user.email or ''
553 social_data['user']['email'] = result.user.email or ''
550
554
551 request.session['zigg.social_auth'] = social_data
555 request.session['zigg.social_auth'] = social_data
552 # user is logged so bind his external identity with account
556 # user is logged so bind his external identity with account
553 if request.user:
557 if request.user:
554 handle_social_data(request, request.user, social_data)
558 handle_social_data(request, request.user, social_data)
555 request.session.pop('zigg.social_auth', None)
559 request.session.pop('zigg.social_auth', None)
556 return HTTPFound(location=request.route_url('/'))
560 return HTTPFound(location=request.route_url('/'))
557 else:
561 else:
558 user = ExternalIdentityService.user_by_external_id_and_provider(
562 user = ExternalIdentityService.user_by_external_id_and_provider(
559 social_data['user']['id'],
563 social_data['user']['id'],
560 social_data['credentials'].provider_name
564 social_data['credentials'].provider_name
561 )
565 )
562 # fix legacy accounts with wrong google ID
566 # fix legacy accounts with wrong google ID
563 if not user and social_data['credentials'].provider_name == 'google':
567 if not user and social_data['credentials'].provider_name == 'google':
564 user = ExternalIdentityService.user_by_external_id_and_provider(
568 user = ExternalIdentityService.user_by_external_id_and_provider(
565 social_data['user']['email'],
569 social_data['user']['email'],
566 social_data['credentials'].provider_name)
570 social_data['credentials'].provider_name)
567
571
568 # user tokens are already found in our db
572 # user tokens are already found in our db
569 if user:
573 if user:
570 handle_social_data(request, user, social_data)
574 handle_social_data(request, user, social_data)
571 headers = security.remember(request, user.id)
575 headers = security.remember(request, user.id)
572 request.session.pop('zigg.social_auth', None)
576 request.session.pop('zigg.social_auth', None)
573 return HTTPFound(location=request.route_url('/'), headers=headers)
577 return HTTPFound(location=request.route_url('/'), headers=headers)
574 else:
578 else:
575 msg = 'You need to finish registration ' \
579 msg = 'You need to finish registration ' \
576 'process to bind your external identity to your account ' \
580 'process to bind your external identity to your account ' \
577 'or sign in to existing account'
581 'or sign in to existing account'
578 request.session.flash(msg)
582 request.session.flash(msg)
579 return HTTPFound(location=request.route_url('register'))
583 return HTTPFound(location=request.route_url('register'))
580
584
581
585
582 @view_config(route_name='section_view', permission='authenticated',
586 @view_config(route_name='section_view', permission='authenticated',
583 match_param=['section=users_section', 'view=search_users'],
587 match_param=['section=users_section', 'view=search_users'],
584 renderer='json')
588 renderer='json')
585 def search_users(request):
589 def search_users(request):
586 """
590 """
587 Returns a list of users for autocomplete
591 Returns a list of users for autocomplete
588 """
592 """
589 user = request.user
593 user = request.user
590 items_returned = []
594 items_returned = []
591 like_condition = request.params.get('user_name', '') + '%'
595 like_condition = request.params.get('user_name', '') + '%'
592 # first append used if email is passed
596 # first append used if email is passed
593 found_user = User.by_email(request.params.get('user_name', ''))
597 found_user = User.by_email(request.params.get('user_name', ''))
594 if found_user:
598 if found_user:
595 name = '{} {}'.format(found_user.first_name, found_user.last_name)
599 name = '{} {}'.format(found_user.first_name, found_user.last_name)
596 items_returned.append({'user': found_user.user_name, 'name': name})
600 items_returned.append({'user': found_user.user_name, 'name': name})
597 for found_user in User.user_names_like(like_condition).limit(20):
601 for found_user in User.user_names_like(like_condition).limit(20):
598 name = '{} {}'.format(found_user.first_name, found_user.last_name)
602 name = '{} {}'.format(found_user.first_name, found_user.last_name)
599 items_returned.append({'user': found_user.user_name, 'name': name})
603 items_returned.append({'user': found_user.user_name, 'name': name})
600 return items_returned
604 return items_returned
601
605
602
606
603 @view_config(route_name='users_self_property', match_param='key=auth_tokens',
607 @view_config(route_name='users_self_property', match_param='key=auth_tokens',
604 request_method="GET", renderer='json', permission='authenticated')
608 request_method="GET", renderer='json', permission='authenticated')
605 @view_config(route_name='users_property', match_param='key=auth_tokens',
609 @view_config(route_name='users_property', match_param='key=auth_tokens',
606 request_method="GET", renderer='json', permission='authenticated')
610 request_method="GET", renderer='json', permission='authenticated')
607 def auth_tokens_list(request):
611 def auth_tokens_list(request):
608 """
612 """
609 Lists all available alert channels
613 Lists all available alert channels
610 """
614 """
611 if request.matched_route.name == 'users_self_property':
615 if request.matched_route.name == 'users_self_property':
612 user = request.user
616 user = request.user
613 else:
617 else:
614 user = User.by_id(request.matchdict.get('user_id'))
618 user = User.by_id(request.matchdict.get('user_id'))
615 if not user:
619 if not user:
616 return HTTPNotFound()
620 return HTTPNotFound()
617 return [c.get_dict() for c in user.auth_tokens]
621 return [c.get_dict() for c in user.auth_tokens]
618
622
619
623
620 @view_config(route_name='users_self_property', match_param='key=auth_tokens',
624 @view_config(route_name='users_self_property', match_param='key=auth_tokens',
621 request_method="POST", renderer='json',
625 request_method="POST", renderer='json',
622 permission='authenticated')
626 permission='authenticated')
623 @view_config(route_name='users_property', match_param='key=auth_tokens',
627 @view_config(route_name='users_property', match_param='key=auth_tokens',
624 request_method="POST", renderer='json',
628 request_method="POST", renderer='json',
625 permission='authenticated')
629 permission='authenticated')
626 def auth_tokens_POST(request):
630 def auth_tokens_POST(request):
627 """
631 """
628 Lists all available alert channels
632 Lists all available alert channels
629 """
633 """
630 if request.matched_route.name == 'users_self_property':
634 if request.matched_route.name == 'users_self_property':
631 user = request.user
635 user = request.user
632 else:
636 else:
633 user = User.by_id(request.matchdict.get('user_id'))
637 user = User.by_id(request.matchdict.get('user_id'))
634 if not user:
638 if not user:
635 return HTTPNotFound()
639 return HTTPNotFound()
636
640
637 req_data = request.safe_json_body or {}
641 req_data = request.safe_json_body or {}
638 if not req_data.get('expires'):
642 if not req_data.get('expires'):
639 req_data.pop('expires', None)
643 req_data.pop('expires', None)
640 form = forms.AuthTokenCreateForm(MultiDict(req_data), csrf_context=request)
644 form = forms.AuthTokenCreateForm(MultiDict(req_data), csrf_context=request)
641 if not form.validate():
645 if not form.validate():
642 return HTTPUnprocessableEntity(body=form.errors_json)
646 return HTTPUnprocessableEntity(body=form.errors_json)
643 token = AuthToken()
647 token = AuthToken()
644 form.populate_obj(token)
648 form.populate_obj(token)
645 if token.expires:
649 if token.expires:
646 interval = h.time_deltas.get(token.expires)['delta']
650 interval = h.time_deltas.get(token.expires)['delta']
647 token.expires = datetime.datetime.utcnow() + interval
651 token.expires = datetime.datetime.utcnow() + interval
648 user.auth_tokens.append(token)
652 user.auth_tokens.append(token)
649 DBSession.flush()
653 DBSession.flush()
650 return token.get_dict()
654 return token.get_dict()
651
655
652
656
653 @view_config(route_name='users_self_property', match_param='key=auth_tokens',
657 @view_config(route_name='users_self_property', match_param='key=auth_tokens',
654 request_method="DELETE", renderer='json',
658 request_method="DELETE", renderer='json',
655 permission='authenticated')
659 permission='authenticated')
656 @view_config(route_name='users_property', match_param='key=auth_tokens',
660 @view_config(route_name='users_property', match_param='key=auth_tokens',
657 request_method="DELETE", renderer='json',
661 request_method="DELETE", renderer='json',
658 permission='authenticated')
662 permission='authenticated')
659 def auth_tokens_DELETE(request):
663 def auth_tokens_DELETE(request):
660 """
664 """
661 Lists all available alert channels
665 Lists all available alert channels
662 """
666 """
663 if request.matched_route.name == 'users_self_property':
667 if request.matched_route.name == 'users_self_property':
664 user = request.user
668 user = request.user
665 else:
669 else:
666 user = User.by_id(request.matchdict.get('user_id'))
670 user = User.by_id(request.matchdict.get('user_id'))
667 if not user:
671 if not user:
668 return HTTPNotFound()
672 return HTTPNotFound()
669
673
670 for token in user.auth_tokens:
674 for token in user.auth_tokens:
671 if token.token == request.params.get('token'):
675 if token.token == request.params.get('token'):
672 user.auth_tokens.remove(token)
676 user.auth_tokens.remove(token)
673 return True
677 return True
674 return False
678 return False
General Comments 2
Under Review
author

Auto status change to "Under Review"

Rejected

Please use: https://github.com/Appenlight/appenlight to contribute :) Thanks !

You need to be logged in to leave comments. Login now