security.py
352 lines
| 13.1 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): | ||||
r153 | if userid and hasattr(request, "user") and request.user: | |||
groups = ["group:%s" % g.id for g in request.user.groups] | ||||
r0 | 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): | ||||
r153 | token = request.headers.get("x-appenlight-auth-token") | |||
r0 | if token: | |||
auth_token = AuthTokenService.by_token(token) | ||||
if auth_token and not auth_token.is_expired: | ||||
r153 | log.info("%s is valid" % auth_token) | |||
r0 | return auth_token.owner_id | |||
elif auth_token: | ||||
r153 | log.warning("%s is expired" % auth_token) | |||
r0 | else: | |||
r153 | log.warning("token: %s is not found" % token) | |||
r0 | ||||
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 | ||||
""" | ||||
r153 | if perm_name == "root_administration": | |||
return (Allow, perm_user, ALL_PERMISSIONS) | ||||
r0 | else: | |||
r153 | return (Allow, perm_user, perm_name) | |||
r0 | ||||
def add_root_superperm(request, context): | ||||
""" | ||||
Adds ALL_PERMISSIONS to every resource if user somehow has 'root_permission' | ||||
non-resource permission | ||||
""" | ||||
r153 | 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: | |||
r153 | if perm_name == "root_administration": | |||
r0 | 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): | ||||
r153 | self.__acl__ = [ | |||
(Allow, Authenticated, "authenticated"), | ||||
(Allow, Authenticated, "create_resources"), | ||||
] | ||||
r0 | # general page factory - append custom non resource permissions | |||
r153 | 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)) | ||||
r153 | ||||
r0 | 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__ = [] | ||||
r153 | resource_id = request.matchdict.get( | |||
"resource_id", request.GET.get("resource_id") | ||||
) | ||||
r0 | resource_id = to_integer_safe(resource_id) | |||
r153 | self.resource = ( | |||
ResourceService.by_resource_id(resource_id) if resource_id else None | ||||
) | ||||
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 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__ = [] | ||||
r153 | group_id = request.matchdict.get("group_id", request.params.get("group_id")) | |||
r0 | group_id = to_integer_safe(group_id) | |||
r153 | self.report_group = ReportGroupService.by_id(group_id) if group_id else None | |||
r0 | if not self.report_group: | |||
raise HTTPNotFound() | ||||
self.public = self.report_group.public | ||||
r153 | self.resource = ( | |||
ResourceService.by_resource_id(self.report_group.resource_id) | ||||
if self.report_group | ||||
else None | ||||
) | ||||
r0 | ||||
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: | ||||
r153 | self.__acl__.append((Allow, Everyone, "view")) | |||
r0 | if not request.user: | |||
# unauthed users need to visit using both group and report pair | ||||
r153 | report_id = request.params.get( | |||
"reportId", request.params.get("report_id", -1) | ||||
) | ||||
r0 | 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( | ||||
r153 | "x-appenlight-api-key", request.params.get("api_key") | |||
r0 | ) | |||
log.debug("private key: %s" % private_api_key) | ||||
if private_api_key: | ||||
r153 | self.resource = ApplicationService.by_api_key_cached()(private_api_key) | |||
r0 | # then try public key | |||
else: | ||||
public_api_key = request.headers.get( | ||||
r153 | "x-appenlight-public-api-key", request.GET.get("public_api_key") | |||
) | ||||
r0 | log.debug("public key: %s" % public_api_key) | |||
self.resource = ApplicationService.by_public_api_key( | ||||
r153 | public_api_key, from_cache=True, request=request | |||
) | ||||
r0 | self.possibly_public = True | |||
if self.resource: | ||||
r153 | self.__acl__.append((Allow, Everyone, "create")) | |||
r0 | ||||
class AirbrakeV2APIFactory(object): | ||||
""" | ||||
Check permission based on Airbrake XML report | ||||
""" | ||||
def __init__(self, request): | ||||
self.__acl__ = [] | ||||
self.possibly_public = False | ||||
r153 | fixed_xml_data = "" | |||
r0 | try: | |||
r153 | data = request.GET.get("data") | |||
r0 | if data: | |||
self.possibly_public = True | ||||
except (UnicodeDecodeError, UnicodeEncodeError) as exc: | ||||
r153 | log.warning("Problem parsing Airbrake data: %s, failed decoding" % exc) | |||
r0 | raise HTTPBadRequest() | |||
try: | ||||
if not data: | ||||
data = request.body | ||||
# fix shitty airbrake js client not escaping line method attr | ||||
def repl(input): | ||||
r153 | return "line method=%s file" % quoteattr(input.group(1)) | |||
r0 | ||||
fixed_xml_data = re.sub('line method="(.*?)" file', repl, data) | ||||
root = ElementTree.fromstring(fixed_xml_data) | ||||
except Exception as exc: | ||||
r153 | log.info("Problem parsing Airbrake " "data: %s, trying unquoting" % exc) | |||
r0 | self.possibly_public = True | |||
try: | ||||
root = ElementTree.fromstring(urllib.parse.unquote(fixed_xml_data)) | ||||
except Exception as exc: | ||||
r153 | log.warning( | |||
"Problem parsing Airbrake " "data: %s, failed completly" % exc | ||||
) | ||||
r0 | raise HTTPBadRequest() | |||
self.airbrake_xml_etree = root | ||||
r153 | api_key = root.findtext("api-key", "") | |||
r0 | ||||
self.resource = ApplicationService.by_api_key_cached()(api_key) | ||||
if not self.resource: | ||||
r153 | self.resource = ApplicationService.by_public_api_key( | |||
api_key, from_cache=True, request=request | ||||
) | ||||
r0 | if self.resource: | |||
self.possibly_public = True | ||||
if self.resource: | ||||
r153 | self.__acl__.append((Allow, Everyone, "create")) | |||
r0 | ||||
def parse_sentry_header(header): | ||||
r153 | parsed = header.split(" ", 1)[1].split(",") or [] | |||
return dict([x.strip().split("=") for x in parsed]) | ||||
r0 | ||||
class SentryAPIFactory(object): | ||||
""" | ||||
Check permission based on Sentry payload | ||||
""" | ||||
def __init__(self, request): | ||||
self.__acl__ = [] | ||||
self.possibly_public = False | ||||
r153 | if request.headers.get("X-Sentry-Auth", "").startswith("Sentry"): | |||
header_string = request.headers["X-Sentry-Auth"] | ||||
r0 | result = parse_sentry_header(header_string) | |||
r153 | elif request.headers.get("Authorization", "").startswith("Sentry"): | |||
header_string = request.headers["Authorization"] | ||||
r0 | result = parse_sentry_header(header_string) | |||
else: | ||||
r153 | 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)) | ||||
r0 | ||||
self.resource = ApplicationService.by_api_key_cached()(key) | ||||
r153 | if not self.resource or result.get("sentry_client", "").startswith("raven-js"): | |||
r0 | self.resource = ApplicationService.by_public_api_key( | |||
r153 | key, from_cache=True, request=request | |||
) | ||||
r0 | if self.resource: | |||
r153 | self.__acl__.append((Allow, Everyone, "create")) | |||
r0 | ||||
class ResourcePluginConfigFactory(object): | ||||
def __init__(self, request): | ||||
Resource = appenlight.models.resource.Resource | ||||
self.__acl__ = [] | ||||
self.resource = None | ||||
r153 | plugin_id = to_integer_safe(request.matchdict.get("id")) | |||
r0 | 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__ = [] | ||||
r153 | resource_id = request.unsafe_json_body().get("resource_id") | |||
r0 | 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: | ||||
r153 | resource_id = json_body.get("resource_id") | |||
r0 | else: | |||
r153 | resource_id = request.GET.get("resource_id") | |||
r0 | 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) | ||||