|
|
# -*- 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.
|
|
|
|
|
|
from pyramid.security import Allow, Everyone, Authenticated, ALL_PERMISSIONS
|
|
|
from pyramid.authentication import CallbackAuthenticationPolicy
|
|
|
import appenlight.models.resource
|
|
|
from appenlight.models.services.auth_token import AuthTokenService
|
|
|
from appenlight.models.services.application import ApplicationService
|
|
|
from appenlight.models.services.report_group import ReportGroupService
|
|
|
from appenlight.models.services.plugin_config import PluginConfigService
|
|
|
from appenlight.lib import to_integer_safe
|
|
|
from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest
|
|
|
from ziggurat_foundations.permissions import permission_to_04_acls
|
|
|
import defusedxml.ElementTree as ElementTree
|
|
|
import urllib.request, urllib.error, urllib.parse
|
|
|
import logging
|
|
|
import re
|
|
|
from xml.sax.saxutils import quoteattr
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
def groupfinder(userid, request):
|
|
|
if userid and hasattr(request, 'user') and request.user:
|
|
|
groups = ['group:%s' % g.id for g in request.user.groups]
|
|
|
return groups
|
|
|
return []
|
|
|
|
|
|
|
|
|
class AuthTokenAuthenticationPolicy(CallbackAuthenticationPolicy):
|
|
|
def __init__(self, callback=None):
|
|
|
self.callback = callback
|
|
|
|
|
|
def remember(self, request, principal, **kw):
|
|
|
return []
|
|
|
|
|
|
def forget(self, request):
|
|
|
return []
|
|
|
|
|
|
def unauthenticated_userid(self, request):
|
|
|
token = request.headers.get('x-appenlight-auth-token')
|
|
|
if token:
|
|
|
auth_token = AuthTokenService.by_token(token)
|
|
|
if auth_token and not auth_token.is_expired:
|
|
|
log.info('%s is valid' % auth_token)
|
|
|
return auth_token.owner_id
|
|
|
elif auth_token:
|
|
|
log.warning('%s is expired' % auth_token)
|
|
|
else:
|
|
|
log.warning('token: %s is not found' % token)
|
|
|
|
|
|
def authenticated_userid(self, request):
|
|
|
return self.unauthenticated_userid(request)
|
|
|
|
|
|
|
|
|
def rewrite_root_perm(perm_user, perm_name):
|
|
|
"""
|
|
|
Translates root_administration into ALL_PERMISSIONS object
|
|
|
"""
|
|
|
if perm_name == 'root_administration':
|
|
|
return (Allow, perm_user, ALL_PERMISSIONS,)
|
|
|
else:
|
|
|
return (Allow, perm_user, perm_name,)
|
|
|
|
|
|
|
|
|
def add_root_superperm(request, context):
|
|
|
"""
|
|
|
Adds ALL_PERMISSIONS to every resource if user somehow has 'root_permission'
|
|
|
non-resource permission
|
|
|
"""
|
|
|
if hasattr(request, 'user') and request.user:
|
|
|
acls = permission_to_04_acls(request.user.permissions)
|
|
|
for perm_user, perm_name in acls:
|
|
|
if perm_name == 'root_administration':
|
|
|
context.__acl__.append(rewrite_root_perm(perm_user, perm_name))
|
|
|
|
|
|
|
|
|
class RootFactory(object):
|
|
|
"""
|
|
|
General factory for non-resource/report specific pages
|
|
|
"""
|
|
|
|
|
|
def __init__(self, request):
|
|
|
self.__acl__ = [(Allow, Authenticated, 'authenticated'),
|
|
|
(Allow, Authenticated, 'create_resources')]
|
|
|
# general page factory - append custom non resource permissions
|
|
|
if hasattr(request, 'user') and request.user:
|
|
|
acls = permission_to_04_acls(request.user.permissions)
|
|
|
for perm_user, perm_name in acls:
|
|
|
self.__acl__.append(rewrite_root_perm(perm_user, perm_name))
|
|
|
|
|
|
class ResourceFactory(object):
|
|
|
"""
|
|
|
Checks permissions to specific resource based on user permissions or
|
|
|
API key headers
|
|
|
"""
|
|
|
|
|
|
def __init__(self, request):
|
|
|
Resource = appenlight.models.resource.Resource
|
|
|
|
|
|
self.__acl__ = []
|
|
|
resource_id = request.matchdict.get("resource_id",
|
|
|
request.GET.get("resource_id"))
|
|
|
resource_id = to_integer_safe(resource_id)
|
|
|
self.resource = Resource.by_resource_id(resource_id) \
|
|
|
if resource_id else None
|
|
|
if self.resource and request.user:
|
|
|
self.__acl__ = self.resource.__acl__
|
|
|
permissions = self.resource.perms_for_user(request.user)
|
|
|
for perm_user, perm_name in permission_to_04_acls(permissions):
|
|
|
self.__acl__.append(rewrite_root_perm(perm_user, perm_name))
|
|
|
add_root_superperm(request, self)
|
|
|
|
|
|
|
|
|
class ResourceReportFactory(object):
|
|
|
"""
|
|
|
Checks permissions to specific resource based on user permissions or
|
|
|
API key headers
|
|
|
Resource is fetched based on report group information
|
|
|
"""
|
|
|
|
|
|
def __init__(self, request):
|
|
|
Resource = appenlight.models.resource.Resource
|
|
|
|
|
|
self.__acl__ = []
|
|
|
group_id = request.matchdict.get("group_id",
|
|
|
request.params.get("group_id"))
|
|
|
group_id = to_integer_safe(group_id)
|
|
|
self.report_group = ReportGroupService.by_id(
|
|
|
group_id) if group_id else None
|
|
|
if not self.report_group:
|
|
|
raise HTTPNotFound()
|
|
|
|
|
|
self.public = self.report_group.public
|
|
|
self.resource = Resource.by_resource_id(self.report_group.resource_id) \
|
|
|
if self.report_group else None
|
|
|
|
|
|
if self.resource:
|
|
|
self.__acl__ = self.resource.__acl__
|
|
|
if request.user:
|
|
|
permissions = self.resource.perms_for_user(request.user)
|
|
|
for perm_user, perm_name in permission_to_04_acls(permissions):
|
|
|
self.__acl__.append(rewrite_root_perm(perm_user, perm_name))
|
|
|
if self.public:
|
|
|
self.__acl__.append((Allow, Everyone, 'view',))
|
|
|
if not request.user:
|
|
|
# unauthed users need to visit using both group and report pair
|
|
|
report_id = request.params.get('reportId',
|
|
|
request.params.get('report_id', -1))
|
|
|
report = self.report_group.get_report(report_id, public=True)
|
|
|
if not report:
|
|
|
raise HTTPNotFound()
|
|
|
add_root_superperm(request, self)
|
|
|
|
|
|
|
|
|
class APIFactory(object):
|
|
|
"""
|
|
|
Checks permissions to perform client API actions based on keys
|
|
|
"""
|
|
|
|
|
|
def __init__(self, request):
|
|
|
self.__acl__ = []
|
|
|
self.possibly_public = False
|
|
|
private_api_key = request.headers.get(
|
|
|
'x-appenlight-api-key',
|
|
|
request.params.get('api_key')
|
|
|
)
|
|
|
log.debug("private key: %s" % private_api_key)
|
|
|
if private_api_key:
|
|
|
self.resource = ApplicationService.by_api_key_cached()(
|
|
|
private_api_key)
|
|
|
# then try public key
|
|
|
else:
|
|
|
public_api_key = request.headers.get(
|
|
|
'x-appenlight-public-api-key',
|
|
|
request.GET.get('public_api_key'))
|
|
|
log.debug("public key: %s" % public_api_key)
|
|
|
self.resource = ApplicationService.by_public_api_key(
|
|
|
public_api_key, from_cache=True, request=request)
|
|
|
self.possibly_public = True
|
|
|
if self.resource:
|
|
|
self.__acl__.append((Allow, Everyone, 'create',))
|
|
|
|
|
|
|
|
|
class AirbrakeV2APIFactory(object):
|
|
|
"""
|
|
|
Check permission based on Airbrake XML report
|
|
|
"""
|
|
|
|
|
|
def __init__(self, request):
|
|
|
self.__acl__ = []
|
|
|
self.possibly_public = False
|
|
|
fixed_xml_data = ''
|
|
|
try:
|
|
|
data = request.GET.get('data')
|
|
|
if data:
|
|
|
self.possibly_public = True
|
|
|
except (UnicodeDecodeError, UnicodeEncodeError) as exc:
|
|
|
log.warning(
|
|
|
'Problem parsing Airbrake data: %s, failed decoding' % exc)
|
|
|
raise HTTPBadRequest()
|
|
|
try:
|
|
|
if not data:
|
|
|
data = request.body
|
|
|
# fix shitty airbrake js client not escaping line method attr
|
|
|
|
|
|
def repl(input):
|
|
|
return 'line method=%s file' % quoteattr(input.group(1))
|
|
|
|
|
|
fixed_xml_data = re.sub('line method="(.*?)" file', repl, data)
|
|
|
root = ElementTree.fromstring(fixed_xml_data)
|
|
|
except Exception as exc:
|
|
|
log.info(
|
|
|
'Problem parsing Airbrake '
|
|
|
'data: %s, trying unquoting' % exc)
|
|
|
self.possibly_public = True
|
|
|
try:
|
|
|
root = ElementTree.fromstring(urllib.parse.unquote(fixed_xml_data))
|
|
|
except Exception as exc:
|
|
|
log.warning('Problem parsing Airbrake '
|
|
|
'data: %s, failed completly' % exc)
|
|
|
raise HTTPBadRequest()
|
|
|
self.airbrake_xml_etree = root
|
|
|
api_key = root.findtext('api-key', '')
|
|
|
|
|
|
self.resource = ApplicationService.by_api_key_cached()(api_key)
|
|
|
if not self.resource:
|
|
|
self.resource = ApplicationService.by_public_api_key(api_key,
|
|
|
from_cache=True,
|
|
|
request=request)
|
|
|
if self.resource:
|
|
|
self.possibly_public = True
|
|
|
|
|
|
if self.resource:
|
|
|
self.__acl__.append((Allow, Everyone, 'create',))
|
|
|
|
|
|
|
|
|
def parse_sentry_header(header):
|
|
|
parsed = header.split(' ', 1)[1].split(',') or []
|
|
|
return dict([x.strip().split('=') for x in parsed])
|
|
|
|
|
|
|
|
|
class SentryAPIFactory(object):
|
|
|
"""
|
|
|
Check permission based on Sentry payload
|
|
|
"""
|
|
|
|
|
|
def __init__(self, request):
|
|
|
self.__acl__ = []
|
|
|
self.possibly_public = False
|
|
|
if request.headers.get('X-Sentry-Auth', '').startswith('Sentry'):
|
|
|
header_string = request.headers['X-Sentry-Auth']
|
|
|
result = parse_sentry_header(header_string)
|
|
|
elif request.headers.get('Authorization', '').startswith('Sentry'):
|
|
|
header_string = request.headers['Authorization']
|
|
|
result = parse_sentry_header(header_string)
|
|
|
else:
|
|
|
result = dict((k, v) for k, v in list(request.GET.items())
|
|
|
if k.startswith('sentry_'))
|
|
|
key = result.get('sentry_key')
|
|
|
log.info('sentry request {}'.format(result))
|
|
|
|
|
|
self.resource = ApplicationService.by_api_key_cached()(key)
|
|
|
if not self.resource or \
|
|
|
result.get('sentry_client', '').startswith('raven-js'):
|
|
|
self.resource = ApplicationService.by_public_api_key(
|
|
|
key, from_cache=True, request=request)
|
|
|
if self.resource:
|
|
|
self.__acl__.append((Allow, Everyone, 'create',))
|
|
|
|
|
|
|
|
|
class ResourcePluginConfigFactory(object):
|
|
|
|
|
|
def __init__(self, request):
|
|
|
Resource = appenlight.models.resource.Resource
|
|
|
self.__acl__ = []
|
|
|
self.resource = None
|
|
|
plugin_id = to_integer_safe(request.matchdict.get('id'))
|
|
|
self.plugin = PluginConfigService.by_id(plugin_id)
|
|
|
if not self.plugin:
|
|
|
raise HTTPNotFound()
|
|
|
if self.plugin.resource_id:
|
|
|
self.resource = Resource.by_resource_id(self.plugin.resource_id)
|
|
|
if self.resource:
|
|
|
self.__acl__ = self.resource.__acl__
|
|
|
if request.user and self.resource:
|
|
|
permissions = self.resource.perms_for_user(request.user)
|
|
|
for perm_user, perm_name in permission_to_04_acls(permissions):
|
|
|
self.__acl__.append(rewrite_root_perm(perm_user, perm_name))
|
|
|
|
|
|
add_root_superperm(request, self)
|
|
|
|
|
|
|
|
|
class ResourceJSONBodyFactory(object):
|
|
|
"""
|
|
|
Checks permissions to specific resource based on user permissions or
|
|
|
API key headers from json body
|
|
|
"""
|
|
|
|
|
|
def __init__(self, request):
|
|
|
Resource = appenlight.models.resource.Resource
|
|
|
|
|
|
self.__acl__ = []
|
|
|
resource_id = request.unsafe_json_body().get('resource_id')
|
|
|
resource_id = to_integer_safe(resource_id)
|
|
|
self.resource = Resource.by_resource_id(resource_id)
|
|
|
if self.resource and request.user:
|
|
|
self.__acl__ = self.resource.__acl__
|
|
|
permissions = self.resource.perms_for_user(request.user)
|
|
|
for perm_user, perm_name in permission_to_04_acls(permissions):
|
|
|
self.__acl__.append(rewrite_root_perm(perm_user, perm_name))
|
|
|
add_root_superperm(request, self)
|
|
|
|
|
|
|
|
|
class ResourcePluginMixedFactory(object):
|
|
|
def __init__(self, request):
|
|
|
Resource = appenlight.models.resource.Resource
|
|
|
self.__acl__ = []
|
|
|
json_body = request.safe_json_body
|
|
|
self.resource = None
|
|
|
if json_body:
|
|
|
resource_id = json_body.get('resource_id')
|
|
|
else:
|
|
|
resource_id = request.GET.get('resource_id')
|
|
|
if resource_id:
|
|
|
resource_id = to_integer_safe(resource_id)
|
|
|
self.resource = Resource.by_resource_id(resource_id)
|
|
|
if self.resource and request.user:
|
|
|
self.__acl__ = self.resource.__acl__
|
|
|
permissions = self.resource.perms_for_user(request.user)
|
|
|
for perm_user, perm_name in permission_to_04_acls(permissions):
|
|
|
self.__acl__.append(rewrite_root_perm(perm_user, perm_name))
|
|
|
add_root_superperm(request, self)
|
|
|
|