security.py
348 lines
| 13.3 KiB
| text/x-python
|
PythonLexer
r0 | # -*- coding: utf-8 -*- | |||
r112 | # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors | |||
r0 | # | |||
r112 | # 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 | ||||
r0 | # | |||
r112 | # http://www.apache.org/licenses/LICENSE-2.0 | |||
r0 | # | |||
r112 | # 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. | ||||
r0 | ||||
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 | ||||
r135 | from ziggurat_foundations.models.services.user import UserService | |||
from ziggurat_foundations.models.services.resource import ResourceService | ||||
r0 | 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: | ||||
r135 | acls = permission_to_04_acls(UserService.permissions(request.user)) | |||
r0 | 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: | ||||
r135 | acls = permission_to_04_acls(UserService.permissions(request.user)) | |||
r0 | 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) | ||||
r135 | self.resource = ResourceService.by_resource_id(resource_id) \ | |||
r0 | if resource_id else None | |||
if self.resource and request.user: | ||||
self.__acl__ = self.resource.__acl__ | ||||
r135 | permissions = ResourceService.perms_for_user(self.resource, request.user) | |||
r0 | 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 | ||||
r135 | self.resource = ResourceService.by_resource_id(self.report_group.resource_id) \ | |||
r0 | if self.report_group else None | |||
if self.resource: | ||||
self.__acl__ = self.resource.__acl__ | ||||
if request.user: | ||||
r135 | permissions = ResourceService.perms_for_user(self.resource, request.user) | |||
r0 | 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: | ||||
r135 | self.resource = ResourceService.by_resource_id(self.plugin.resource_id) | |||
r0 | if self.resource: | |||
self.__acl__ = self.resource.__acl__ | ||||
if request.user and self.resource: | ||||
r135 | permissions = ResourceService.perms_for_user(self.resource, request.user) | |||
r0 | 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) | ||||
r135 | self.resource = ResourceService.by_resource_id(resource_id) | |||
r0 | if self.resource and request.user: | |||
self.__acl__ = self.resource.__acl__ | ||||
r135 | permissions = ResourceService.perms_for_user(self.resource, request.user) | |||
r0 | 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) | ||||
r135 | self.resource = ResourceService.by_resource_id(resource_id) | |||
r0 | if self.resource and request.user: | |||
self.__acl__ = self.resource.__acl__ | ||||
r135 | permissions = ResourceService.perms_for_user(self.resource, request.user) | |||
r0 | 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) | ||||