|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
# Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
|
|
|
#
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
# You may obtain a copy of the License at
|
|
|
#
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
#
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
# See the License for the specific language governing permissions and
|
|
|
# limitations under the License.
|
|
|
|
|
|
import colander
|
|
|
import datetime
|
|
|
import json
|
|
|
import logging
|
|
|
import uuid
|
|
|
import pyramid.security as security
|
|
|
import appenlight.lib.helpers as h
|
|
|
|
|
|
from authomatic.adapters import WebObAdapter
|
|
|
from pyramid.view import view_config
|
|
|
from pyramid.httpexceptions import HTTPFound, HTTPUnprocessableEntity
|
|
|
from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest
|
|
|
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
|
from ziggurat_foundations.models.services.external_identity import (
|
|
|
ExternalIdentityService,
|
|
|
)
|
|
|
from ziggurat_foundations.models.services.user import UserService
|
|
|
|
|
|
from appenlight.lib import generate_random_string
|
|
|
from appenlight.lib.social import handle_social_data
|
|
|
from appenlight.lib.utils import (
|
|
|
channelstream_request,
|
|
|
add_cors_headers,
|
|
|
permission_tuple_to_dict,
|
|
|
)
|
|
|
from appenlight.models import DBSession
|
|
|
from appenlight.models.alert_channels.email import EmailAlertChannel
|
|
|
from appenlight.models.alert_channel_action import AlertChannelAction
|
|
|
from appenlight.models.services.alert_channel import AlertChannelService
|
|
|
from appenlight.models.services.alert_channel_action import AlertChannelActionService
|
|
|
from appenlight.models.auth_token import AuthToken
|
|
|
from appenlight.models.report import REPORT_TYPE_MATRIX
|
|
|
from appenlight.models.user import User
|
|
|
from appenlight.models.services.user import UserService
|
|
|
from appenlight.subscribers import _
|
|
|
from appenlight.validators import build_rule_schema
|
|
|
from appenlight import forms
|
|
|
from webob.multidict import MultiDict
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_no_id",
|
|
|
renderer="json",
|
|
|
request_method="GET",
|
|
|
permission="root_administration",
|
|
|
)
|
|
|
def users_list(request):
|
|
|
"""
|
|
|
Returns users list
|
|
|
"""
|
|
|
props = [
|
|
|
"user_name",
|
|
|
"id",
|
|
|
"first_name",
|
|
|
"last_name",
|
|
|
"email",
|
|
|
"last_login_date",
|
|
|
"status",
|
|
|
]
|
|
|
users = UserService.all()
|
|
|
users_dicts = []
|
|
|
for user in users:
|
|
|
u_dict = user.get_dict(include_keys=props)
|
|
|
u_dict["gravatar_url"] = UserService.gravatar_url(user, s=20)
|
|
|
users_dicts.append(u_dict)
|
|
|
return users_dicts
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_no_id",
|
|
|
renderer="json",
|
|
|
request_method="POST",
|
|
|
permission="root_administration",
|
|
|
)
|
|
|
def users_create(request):
|
|
|
"""
|
|
|
Returns users list
|
|
|
"""
|
|
|
form = forms.UserCreateForm(
|
|
|
MultiDict(request.safe_json_body or {}), csrf_context=request
|
|
|
)
|
|
|
if form.validate():
|
|
|
log.info("registering user")
|
|
|
# probably not needed in the future since this requires root anyways
|
|
|
# lets keep this here in case we lower view permission in the future
|
|
|
# if request.registry.settings['appenlight.disable_registration']:
|
|
|
# return HTTPUnprocessableEntity(body={'error': 'Registration is currently disabled.'})
|
|
|
user = User()
|
|
|
# insert new user here
|
|
|
DBSession.add(user)
|
|
|
form.populate_obj(user)
|
|
|
UserService.regenerate_security_code(user)
|
|
|
UserService.set_password(user, user.user_password)
|
|
|
user.status = 1 if form.status.data else 0
|
|
|
request.session.flash(_("User created"))
|
|
|
DBSession.flush()
|
|
|
return user.get_dict(
|
|
|
exclude_keys=[
|
|
|
"security_code_date",
|
|
|
"notes",
|
|
|
"security_code",
|
|
|
"user_password",
|
|
|
]
|
|
|
)
|
|
|
else:
|
|
|
return HTTPUnprocessableEntity(body=form.errors_json)
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users",
|
|
|
renderer="json",
|
|
|
request_method="GET",
|
|
|
permission="root_administration",
|
|
|
)
|
|
|
@view_config(
|
|
|
route_name="users",
|
|
|
renderer="json",
|
|
|
request_method="PATCH",
|
|
|
permission="root_administration",
|
|
|
)
|
|
|
def users_update(request):
|
|
|
"""
|
|
|
Updates user object
|
|
|
"""
|
|
|
user = UserService.by_id(request.matchdict.get("user_id"))
|
|
|
if not user:
|
|
|
return HTTPNotFound()
|
|
|
post_data = request.safe_json_body or {}
|
|
|
if request.method == "PATCH":
|
|
|
form = forms.UserUpdateForm(MultiDict(post_data), csrf_context=request)
|
|
|
if form.validate():
|
|
|
form.populate_obj(user, ignore_none=True)
|
|
|
if form.user_password.data:
|
|
|
UserService.set_password(user, user.user_password)
|
|
|
if form.status.data:
|
|
|
user.status = 1
|
|
|
else:
|
|
|
user.status = 0
|
|
|
else:
|
|
|
return HTTPUnprocessableEntity(body=form.errors_json)
|
|
|
return user.get_dict(
|
|
|
exclude_keys=["security_code_date", "notes", "security_code", "user_password"]
|
|
|
)
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_property",
|
|
|
match_param="key=resource_permissions",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def users_resource_permissions_list(request):
|
|
|
"""
|
|
|
Get list of permissions assigned to specific resources
|
|
|
"""
|
|
|
user = UserService.by_id(request.matchdict.get("user_id"))
|
|
|
if not user:
|
|
|
return HTTPNotFound()
|
|
|
return [
|
|
|
permission_tuple_to_dict(perm)
|
|
|
for perm in UserService.resources_with_possible_perms(user)
|
|
|
]
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users",
|
|
|
renderer="json",
|
|
|
request_method="DELETE",
|
|
|
permission="root_administration",
|
|
|
)
|
|
|
def users_DELETE(request):
|
|
|
"""
|
|
|
Removes a user permanently from db - makes a check to see if after the
|
|
|
operation there will be at least one admin left
|
|
|
"""
|
|
|
msg = _("There needs to be at least one administrator in the system")
|
|
|
user = UserService.by_id(request.matchdict.get("user_id"))
|
|
|
if user:
|
|
|
users = UserService.users_for_perms(["root_administration"]).all()
|
|
|
if len(users) < 2 and user.id == users[0].id:
|
|
|
request.session.flash(msg, "warning")
|
|
|
else:
|
|
|
DBSession.delete(user)
|
|
|
request.session.flash(_("User removed"))
|
|
|
return True
|
|
|
request.response.status = 422
|
|
|
return False
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self",
|
|
|
renderer="json",
|
|
|
request_method="GET",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
@view_config(
|
|
|
route_name="users_self",
|
|
|
renderer="json",
|
|
|
request_method="PATCH",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def users_self(request):
|
|
|
"""
|
|
|
Updates user personal information
|
|
|
"""
|
|
|
|
|
|
if request.method == "PATCH":
|
|
|
form = forms.gen_user_profile_form()(
|
|
|
MultiDict(request.unsafe_json_body), csrf_context=request
|
|
|
)
|
|
|
if form.validate():
|
|
|
form.populate_obj(request.user)
|
|
|
request.session.flash(_("Your profile got updated."))
|
|
|
else:
|
|
|
return HTTPUnprocessableEntity(body=form.errors_json)
|
|
|
return request.user.get_dict(
|
|
|
exclude_keys=["security_code_date", "notes", "security_code", "user_password"],
|
|
|
extended_info=True,
|
|
|
)
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
match_param="key=external_identities",
|
|
|
renderer="json",
|
|
|
request_method="GET",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def users_external_identies(request):
|
|
|
user = request.user
|
|
|
identities = [
|
|
|
{"provider": ident.provider_name, "id": ident.external_user_name}
|
|
|
for ident in user.external_identities.all()
|
|
|
]
|
|
|
return identities
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
match_param="key=external_identities",
|
|
|
renderer="json",
|
|
|
request_method="DELETE",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def users_external_identies_DELETE(request):
|
|
|
"""
|
|
|
Unbinds external identities(google,twitter etc.) from user account
|
|
|
"""
|
|
|
user = request.user
|
|
|
for identity in user.external_identities.all():
|
|
|
log.info("found identity %s" % identity)
|
|
|
if identity.provider_name == request.params.get(
|
|
|
"provider"
|
|
|
) and identity.external_user_name == request.params.get("id"):
|
|
|
log.info("remove identity %s" % identity)
|
|
|
DBSession.delete(identity)
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
match_param="key=password",
|
|
|
renderer="json",
|
|
|
request_method="PATCH",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def users_password(request):
|
|
|
"""
|
|
|
Sets new password for user account
|
|
|
"""
|
|
|
user = request.user
|
|
|
form = forms.ChangePasswordForm(
|
|
|
MultiDict(request.unsafe_json_body), csrf_context=request
|
|
|
)
|
|
|
form.old_password.user = user
|
|
|
if form.validate():
|
|
|
UserService.regenerate_security_code(user)
|
|
|
UserService.set_password(user, form.new_password.data)
|
|
|
msg = (
|
|
|
"Your password got updated. " "Next time log in with your new credentials."
|
|
|
)
|
|
|
request.session.flash(_(msg))
|
|
|
return True
|
|
|
else:
|
|
|
return HTTPUnprocessableEntity(body=form.errors_json)
|
|
|
return False
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
match_param="key=websocket",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def users_websocket(request):
|
|
|
"""
|
|
|
Handle authorization of users trying to connect
|
|
|
"""
|
|
|
# handle preflight request
|
|
|
user = request.user
|
|
|
if request.method == "OPTIONS":
|
|
|
res = request.response.body("OK")
|
|
|
add_cors_headers(res)
|
|
|
return res
|
|
|
applications = UserService.resources_with_perms(
|
|
|
user, ["view"], resource_types=["application"]
|
|
|
)
|
|
|
channels = ["app_%s" % app.resource_id for app in applications]
|
|
|
payload = {
|
|
|
"username": user.user_name,
|
|
|
"conn_id": str(uuid.uuid4()),
|
|
|
"channels": channels,
|
|
|
}
|
|
|
settings = request.registry.settings
|
|
|
response = channelstream_request(
|
|
|
settings["cometd.secret"],
|
|
|
"/connect",
|
|
|
payload,
|
|
|
servers=[request.registry.settings["cometd_servers"]],
|
|
|
throw_exceptions=True,
|
|
|
)
|
|
|
return payload
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
request_method="GET",
|
|
|
match_param="key=alert_channels",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def alert_channels(request):
|
|
|
"""
|
|
|
Lists all available alert channels
|
|
|
"""
|
|
|
user = request.user
|
|
|
return [c.get_dict(extended_info=True) for c in user.alert_channels]
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
match_param="key=alert_actions",
|
|
|
request_method="GET",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def alert_actions(request):
|
|
|
"""
|
|
|
Lists all available alert channels
|
|
|
"""
|
|
|
user = request.user
|
|
|
return [r.get_dict(extended_info=True) for r in user.alert_actions]
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
renderer="json",
|
|
|
match_param="key=alert_channels_rules",
|
|
|
request_method="POST",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def alert_channels_rule_POST(request):
|
|
|
"""
|
|
|
Creates new notification rule for specific alert channel
|
|
|
"""
|
|
|
user = request.user
|
|
|
alert_action = AlertChannelAction(owner_id=request.user.id, type="report")
|
|
|
DBSession.add(alert_action)
|
|
|
DBSession.flush()
|
|
|
return alert_action.get_dict()
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
permission="authenticated",
|
|
|
match_param="key=alert_channels_rules",
|
|
|
renderer="json",
|
|
|
request_method="DELETE",
|
|
|
)
|
|
|
def alert_channels_rule_DELETE(request):
|
|
|
"""
|
|
|
Removes specific alert channel rule
|
|
|
"""
|
|
|
user = request.user
|
|
|
rule_action = AlertChannelActionService.by_owner_id_and_pkey(
|
|
|
user.id, request.GET.get("pkey")
|
|
|
)
|
|
|
if rule_action:
|
|
|
DBSession.delete(rule_action)
|
|
|
return True
|
|
|
return HTTPNotFound()
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
permission="authenticated",
|
|
|
match_param="key=alert_channels_rules",
|
|
|
renderer="json",
|
|
|
request_method="PATCH",
|
|
|
)
|
|
|
def alert_channels_rule_PATCH(request):
|
|
|
"""
|
|
|
Removes specific alert channel rule
|
|
|
"""
|
|
|
user = request.user
|
|
|
json_body = request.unsafe_json_body
|
|
|
|
|
|
schema = build_rule_schema(json_body["rule"], REPORT_TYPE_MATRIX)
|
|
|
try:
|
|
|
schema.deserialize(json_body["rule"])
|
|
|
except colander.Invalid as exc:
|
|
|
return HTTPUnprocessableEntity(body=json.dumps(exc.asdict()))
|
|
|
|
|
|
rule_action = AlertChannelActionService.by_owner_id_and_pkey(
|
|
|
user.id, request.GET.get("pkey")
|
|
|
)
|
|
|
|
|
|
if rule_action:
|
|
|
rule_action.rule = json_body["rule"]
|
|
|
rule_action.resource_id = json_body["resource_id"]
|
|
|
rule_action.action = json_body["action"]
|
|
|
return rule_action.get_dict()
|
|
|
return HTTPNotFound()
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
permission="authenticated",
|
|
|
match_param="key=alert_channels",
|
|
|
renderer="json",
|
|
|
request_method="PATCH",
|
|
|
)
|
|
|
def alert_channels_PATCH(request):
|
|
|
user = request.user
|
|
|
channel_name = request.GET.get("channel_name")
|
|
|
channel_value = request.GET.get("channel_value")
|
|
|
# iterate over channels
|
|
|
channel = None
|
|
|
for channel in user.alert_channels:
|
|
|
if (
|
|
|
channel.channel_name == channel_name
|
|
|
and channel.channel_value == channel_value
|
|
|
):
|
|
|
break
|
|
|
if not channel:
|
|
|
return HTTPNotFound()
|
|
|
|
|
|
allowed_keys = ["daily_digest", "send_alerts"]
|
|
|
for k, v in request.unsafe_json_body.items():
|
|
|
if k in allowed_keys:
|
|
|
setattr(channel, k, v)
|
|
|
else:
|
|
|
return HTTPBadRequest()
|
|
|
return channel.get_dict()
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
permission="authenticated",
|
|
|
match_param="key=alert_channels",
|
|
|
request_method="POST",
|
|
|
renderer="json",
|
|
|
)
|
|
|
def alert_channels_POST(request):
|
|
|
"""
|
|
|
Creates a new email alert channel for user, sends a validation email
|
|
|
"""
|
|
|
user = request.user
|
|
|
form = forms.EmailChannelCreateForm(
|
|
|
MultiDict(request.unsafe_json_body), csrf_context=request
|
|
|
)
|
|
|
if not form.validate():
|
|
|
return HTTPUnprocessableEntity(body=form.errors_json)
|
|
|
|
|
|
email = form.email.data.strip()
|
|
|
channel = EmailAlertChannel()
|
|
|
channel.channel_name = "email"
|
|
|
channel.channel_value = email
|
|
|
security_code = generate_random_string(10)
|
|
|
channel.channel_json_conf = {"security_code": security_code}
|
|
|
user.alert_channels.append(channel)
|
|
|
|
|
|
email_vars = {
|
|
|
"user": user,
|
|
|
"email": email,
|
|
|
"request": request,
|
|
|
"security_code": security_code,
|
|
|
"email_title": "AppEnlight :: " "Please authorize your email",
|
|
|
}
|
|
|
|
|
|
UserService.send_email(
|
|
|
request,
|
|
|
recipients=[email],
|
|
|
variables=email_vars,
|
|
|
template="/email_templates/authorize_email.jinja2",
|
|
|
)
|
|
|
request.session.flash(_("Your alert channel was " "added to the system."))
|
|
|
request.session.flash(
|
|
|
_(
|
|
|
"You need to authorize your email channel, a message was "
|
|
|
"sent containing necessary information."
|
|
|
),
|
|
|
"warning",
|
|
|
)
|
|
|
DBSession.flush()
|
|
|
channel.get_dict()
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="section_view",
|
|
|
match_param=["section=user_section", "view=alert_channels_authorize"],
|
|
|
renderer="string",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def alert_channels_authorize(request):
|
|
|
"""
|
|
|
Performs alert channel authorization based on auth code sent in email
|
|
|
"""
|
|
|
user = request.user
|
|
|
for channel in user.alert_channels:
|
|
|
security_code = request.params.get("security_code", "")
|
|
|
if channel.channel_json_conf["security_code"] == security_code:
|
|
|
channel.channel_validated = True
|
|
|
request.session.flash(_("Your email was authorized."))
|
|
|
return HTTPFound(location=request.route_url("/"))
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
request_method="DELETE",
|
|
|
match_param="key=alert_channels",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def alert_channel_DELETE(request):
|
|
|
"""
|
|
|
Removes alert channel from users channel
|
|
|
"""
|
|
|
user = request.user
|
|
|
channel = None
|
|
|
for chan in user.alert_channels:
|
|
|
if chan.channel_name == request.params.get(
|
|
|
"channel_name"
|
|
|
) and chan.channel_value == request.params.get("channel_value"):
|
|
|
channel = chan
|
|
|
break
|
|
|
if channel:
|
|
|
user.alert_channels.remove(channel)
|
|
|
request.session.flash(_("Your channel was removed."))
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
permission="authenticated",
|
|
|
match_param="key=alert_channels_actions_binds",
|
|
|
renderer="json",
|
|
|
request_method="POST",
|
|
|
)
|
|
|
def alert_channels_actions_binds_POST(request):
|
|
|
"""
|
|
|
Adds alert action to users channels
|
|
|
"""
|
|
|
user = request.user
|
|
|
json_body = request.unsafe_json_body
|
|
|
channel = AlertChannelService.by_owner_id_and_pkey(
|
|
|
user.id, json_body.get("channel_pkey")
|
|
|
)
|
|
|
|
|
|
rule_action = AlertChannelActionService.by_owner_id_and_pkey(
|
|
|
user.id, json_body.get("action_pkey")
|
|
|
)
|
|
|
|
|
|
if channel and rule_action:
|
|
|
if channel.pkey not in [c.pkey for c in rule_action.channels]:
|
|
|
rule_action.channels.append(channel)
|
|
|
return rule_action.get_dict(extended_info=True)
|
|
|
return HTTPUnprocessableEntity()
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
request_method="DELETE",
|
|
|
match_param="key=alert_channels_actions_binds",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def alert_channels_actions_binds_DELETE(request):
|
|
|
"""
|
|
|
Removes alert action from users channels
|
|
|
"""
|
|
|
user = request.user
|
|
|
channel = AlertChannelService.by_owner_id_and_pkey(
|
|
|
user.id, request.GET.get("channel_pkey")
|
|
|
)
|
|
|
|
|
|
rule_action = AlertChannelActionService.by_owner_id_and_pkey(
|
|
|
user.id, request.GET.get("action_pkey")
|
|
|
)
|
|
|
|
|
|
if channel and rule_action:
|
|
|
if channel.pkey in [c.pkey for c in rule_action.channels]:
|
|
|
rule_action.channels.remove(channel)
|
|
|
return rule_action.get_dict(extended_info=True)
|
|
|
return HTTPUnprocessableEntity()
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="social_auth_abort", renderer="string", permission=NO_PERMISSION_REQUIRED
|
|
|
)
|
|
|
def oauth_abort(request):
|
|
|
"""
|
|
|
Handles problems with authorization via velruse
|
|
|
"""
|
|
|
|
|
|
|
|
|
@view_config(route_name="social_auth", permission=NO_PERMISSION_REQUIRED)
|
|
|
def social_auth(request):
|
|
|
# Get the internal provider name URL variable.
|
|
|
provider_name = request.matchdict.get("provider")
|
|
|
|
|
|
# Start the login procedure.
|
|
|
adapter = WebObAdapter(request, request.response)
|
|
|
result = request.authomatic.login(adapter, provider_name)
|
|
|
if result:
|
|
|
if result.error:
|
|
|
return handle_auth_error(request, result)
|
|
|
elif result.user:
|
|
|
return handle_auth_success(request, result)
|
|
|
return request.response
|
|
|
|
|
|
|
|
|
def handle_auth_error(request, result):
|
|
|
# Login procedure finished with an error.
|
|
|
request.session.pop("zigg.social_auth", None)
|
|
|
request.session.flash(
|
|
|
_(
|
|
|
"Something went wrong when we tried to "
|
|
|
"authorize you via external provider. "
|
|
|
"Please try again."
|
|
|
),
|
|
|
"warning",
|
|
|
)
|
|
|
|
|
|
return HTTPFound(location=request.route_url("/"))
|
|
|
|
|
|
|
|
|
def handle_auth_success(request, result):
|
|
|
# Hooray, we have the user!
|
|
|
# OAuth 2.0 and OAuth 1.0a provide only limited user data on login,
|
|
|
# We need to update the user to get more info.
|
|
|
if result.user:
|
|
|
result.user.update()
|
|
|
|
|
|
social_data = {
|
|
|
"user": {"data": result.user.data},
|
|
|
"credentials": result.user.credentials,
|
|
|
}
|
|
|
# normalize data
|
|
|
social_data["user"]["id"] = result.user.id
|
|
|
user_name = result.user.username or ""
|
|
|
# use email name as username for google
|
|
|
if social_data["credentials"].provider_name == "google" and result.user.email:
|
|
|
user_name = result.user.email
|
|
|
social_data["user"]["user_name"] = user_name
|
|
|
social_data["user"]["email"] = result.user.email or ""
|
|
|
|
|
|
request.session["zigg.social_auth"] = social_data
|
|
|
# user is logged so bind his external identity with account
|
|
|
if request.user:
|
|
|
handle_social_data(request, request.user, social_data)
|
|
|
request.session.pop("zigg.social_auth", None)
|
|
|
return HTTPFound(location=request.route_url("/"))
|
|
|
else:
|
|
|
user = ExternalIdentityService.user_by_external_id_and_provider(
|
|
|
social_data["user"]["id"], social_data["credentials"].provider_name
|
|
|
)
|
|
|
# fix legacy accounts with wrong google ID
|
|
|
if not user and social_data["credentials"].provider_name == "google":
|
|
|
user = ExternalIdentityService.user_by_external_id_and_provider(
|
|
|
social_data["user"]["email"], social_data["credentials"].provider_name
|
|
|
)
|
|
|
|
|
|
# user tokens are already found in our db
|
|
|
if user:
|
|
|
handle_social_data(request, user, social_data)
|
|
|
headers = security.remember(request, user.id)
|
|
|
request.session.pop("zigg.social_auth", None)
|
|
|
return HTTPFound(location=request.route_url("/"), headers=headers)
|
|
|
else:
|
|
|
msg = (
|
|
|
"You need to finish registration "
|
|
|
"process to bind your external identity to your account "
|
|
|
"or sign in to existing account"
|
|
|
)
|
|
|
request.session.flash(msg)
|
|
|
return HTTPFound(location=request.route_url("register"))
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="section_view",
|
|
|
permission="authenticated",
|
|
|
match_param=["section=users_section", "view=search_users"],
|
|
|
renderer="json",
|
|
|
)
|
|
|
def search_users(request):
|
|
|
"""
|
|
|
Returns a list of users for autocomplete
|
|
|
"""
|
|
|
user = request.user
|
|
|
items_returned = []
|
|
|
like_condition = request.params.get("user_name", "") + "%"
|
|
|
# first append used if email is passed
|
|
|
found_user = UserService.by_email(request.params.get("user_name", ""))
|
|
|
if found_user:
|
|
|
name = "{} {}".format(found_user.first_name, found_user.last_name)
|
|
|
items_returned.append({"user": found_user.user_name, "name": name})
|
|
|
for found_user in UserService.user_names_like(like_condition).limit(20):
|
|
|
name = "{} {}".format(found_user.first_name, found_user.last_name)
|
|
|
items_returned.append({"user": found_user.user_name, "name": name})
|
|
|
return items_returned
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
match_param="key=auth_tokens",
|
|
|
request_method="GET",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
@view_config(
|
|
|
route_name="users_property",
|
|
|
match_param="key=auth_tokens",
|
|
|
request_method="GET",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def auth_tokens_list(request):
|
|
|
"""
|
|
|
Lists all available alert channels
|
|
|
"""
|
|
|
if request.matched_route.name == "users_self_property":
|
|
|
user = request.user
|
|
|
else:
|
|
|
user = UserService.by_id(request.matchdict.get("user_id"))
|
|
|
if not user:
|
|
|
return HTTPNotFound()
|
|
|
return [c.get_dict() for c in user.auth_tokens]
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
match_param="key=auth_tokens",
|
|
|
request_method="POST",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
@view_config(
|
|
|
route_name="users_property",
|
|
|
match_param="key=auth_tokens",
|
|
|
request_method="POST",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def auth_tokens_POST(request):
|
|
|
"""
|
|
|
Lists all available alert channels
|
|
|
"""
|
|
|
if request.matched_route.name == "users_self_property":
|
|
|
user = request.user
|
|
|
else:
|
|
|
user = UserService.by_id(request.matchdict.get("user_id"))
|
|
|
if not user:
|
|
|
return HTTPNotFound()
|
|
|
|
|
|
req_data = request.safe_json_body or {}
|
|
|
if not req_data.get("expires"):
|
|
|
req_data.pop("expires", None)
|
|
|
form = forms.AuthTokenCreateForm(MultiDict(req_data), csrf_context=request)
|
|
|
if not form.validate():
|
|
|
return HTTPUnprocessableEntity(body=form.errors_json)
|
|
|
token = AuthToken()
|
|
|
form.populate_obj(token)
|
|
|
if token.expires:
|
|
|
interval = h.time_deltas.get(token.expires)["delta"]
|
|
|
token.expires = datetime.datetime.utcnow() + interval
|
|
|
user.auth_tokens.append(token)
|
|
|
DBSession.flush()
|
|
|
return token.get_dict()
|
|
|
|
|
|
|
|
|
@view_config(
|
|
|
route_name="users_self_property",
|
|
|
match_param="key=auth_tokens",
|
|
|
request_method="DELETE",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
@view_config(
|
|
|
route_name="users_property",
|
|
|
match_param="key=auth_tokens",
|
|
|
request_method="DELETE",
|
|
|
renderer="json",
|
|
|
permission="authenticated",
|
|
|
)
|
|
|
def auth_tokens_DELETE(request):
|
|
|
"""
|
|
|
Lists all available alert channels
|
|
|
"""
|
|
|
if request.matched_route.name == "users_self_property":
|
|
|
user = request.user
|
|
|
else:
|
|
|
user = UserService.by_id(request.matchdict.get("user_id"))
|
|
|
if not user:
|
|
|
return HTTPNotFound()
|
|
|
|
|
|
for token in user.auth_tokens:
|
|
|
if token.token == request.params.get("token"):
|
|
|
user.auth_tokens.remove(token)
|
|
|
return True
|
|
|
return False
|
|
|
|