Merge branch 'master' into guardian

This commit is contained in:
Langhammer, Jens 2019-10-25 22:18:13 +02:00
commit feb80049aa
21 changed files with 48 additions and 54 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.6.9-beta current_version = 0.6.11-beta
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)

View File

@ -96,7 +96,7 @@ build-passbook-server:
before_script: before_script:
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
script: script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.6.9-beta - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.6.11-beta
only: only:
- tags - tags
- /^version/.*$/ - /^version/.*$/
@ -108,7 +108,7 @@ build-passbook-static:
before_script: before_script:
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
script: script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.6.9-beta - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.6.11-beta
only: only:
- tags - tags
- /^version/.*$/ - /^version/.*$/
@ -145,8 +145,8 @@ notify-sentry:
before_script: before_script:
- apk add curl - apk add curl
script: script:
- sentry-cli releases new passbook@0.6.9-beta - sentry-cli releases new passbook@0.6.11-beta
- sentry-cli releases set-commits --auto passbook@0.6.9-beta - sentry-cli releases set-commits --auto passbook@0.6.11-beta
only: only:
- tags - tags
- /^version/.*$/ - /^version/.*$/

View File

@ -16,5 +16,8 @@ COPY --from=locker /app/requirements-dev.txt /app/
WORKDIR /app/ WORKDIR /app/
RUN pip install -r requirements.txt --no-cache-dir && \ RUN apt-get update && \
apt-get install -y --no-install-recommends postgresql-client-11 && \
rm -rf /var/lib/apt/ && \
pip install -r requirements.txt --no-cache-dir && \
adduser --system --no-create-home --uid 1000 --group --home /app passbook adduser --system --no-create-home --uid 1000 --group --home /app passbook

View File

@ -39,7 +39,7 @@ http {
gzip on; gzip on;
gzip_types application/javascript image/* text/css; gzip_types application/javascript image/* text/css;
gunzip on; gunzip on;
add_header X-passbook-Version 0.6.9-beta; add_header X-passbook-Version 0.6.11-beta;
add_header Vary X-passbook-Version; add_header Vary X-passbook-Version;
root /data/; root /data/;

View File

@ -8,3 +8,4 @@ threads = 2
enable-threads = true enable-threads = true
uid = passbook uid = passbook
gid = passbook gid = passbook
disable-logging=True

View File

@ -1,6 +1,6 @@
apiVersion: v1 apiVersion: v1
appVersion: "0.6.9-beta" appVersion: "0.6.11-beta"
description: A Helm chart for passbook. description: A Helm chart for passbook.
name: passbook name: passbook
version: "0.6.9-beta" version: "0.6.11-beta"
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png

View File

@ -1,6 +1,6 @@
dependencies: dependencies:
- name: postgresql - name: postgresql
version: 4.2.2 version: 6.3.13
repository: https://kubernetes-charts.storage.googleapis.com/ repository: https://kubernetes-charts.storage.googleapis.com/
- name: redis - name: redis
version: 9.2.1 version: 9.2.1

View File

@ -2,7 +2,7 @@
# This is a YAML-formatted file. # This is a YAML-formatted file.
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
image: image:
tag: 0.6.9-beta tag: 0.6.11-beta
nameOverride: "" nameOverride: ""

View File

@ -1,2 +1,2 @@
"""passbook""" """passbook"""
__version__ = '0.6.9-beta' __version__ = '0.6.11-beta'

View File

@ -122,8 +122,9 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
def form_valid(self, form): def form_valid(self, form):
policy = self.get_object() policy = self.get_object()
user = form.cleaned_data.get('user') user = form.cleaned_data.get('user')
policy_engine = PolicyEngine([policy]) policy_engine = PolicyEngine([policy], user, self.request)
policy_engine.for_user(user).with_request(self.request).build() policy_engine.use_cache = False
policy_engine.build()
result = policy_engine.passing result = policy_engine.passing
if result: if result:
messages.success(self.request, _('User successfully passed policy.')) messages.success(self.request, _('User successfully passed policy.'))

View File

@ -17,8 +17,8 @@ def user_factors(context: RequestContext) -> List[UserSettings]:
matching_factors: List[UserSettings] = [] matching_factors: List[UserSettings] = []
for factor in _all_factors: for factor in _all_factors:
user_settings = factor.user_settings() user_settings = factor.user_settings()
policy_engine = PolicyEngine(factor.policies.all()) policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request'))
policy_engine.for_user(user).with_request(context.get('request')).build() policy_engine.build()
if policy_engine.passing and user_settings: if policy_engine.passing and user_settings:
matching_factors.append(user_settings) matching_factors.append(user_settings)
return matching_factors return matching_factors
@ -31,8 +31,8 @@ def user_sources(context: RequestContext) -> List[UserSettings]:
matching_sources: List[UserSettings] = [] matching_sources: List[UserSettings] = []
for factor in _all_sources: for factor in _all_sources:
user_settings = factor.user_settings() user_settings = factor.user_settings()
policy_engine = PolicyEngine(factor.policies.all()) policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request'))
policy_engine.for_user(user).with_request(context.get('request')).build() policy_engine.build()
if policy_engine.passing and user_settings: if policy_engine.passing and user_settings:
matching_sources.append(user_settings) matching_sources.append(user_settings)
return matching_sources return matching_sources

View File

@ -31,6 +31,6 @@ class AccessMixin:
def user_has_access(self, application: Application, user: User) -> Tuple[bool, List[str]]: def user_has_access(self, application: Application, user: User) -> Tuple[bool, List[str]]:
"""Check if user has access to application.""" """Check if user has access to application."""
LOGGER.debug("Checking permissions", user=user, application=application) LOGGER.debug("Checking permissions", user=user, application=application)
policy_engine = PolicyEngine(application.policies.all()) policy_engine = PolicyEngine(application.policies.all(), user, self.request)
policy_engine.for_user(user).with_request(self.request).build() policy_engine.build()
return policy_engine.result return policy_engine.result

View File

@ -16,8 +16,7 @@ class OverviewView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['applications'] = [] kwargs['applications'] = []
for application in Application.objects.all(): for application in Application.objects.all():
engine = PolicyEngine(application.policies.all()) engine = PolicyEngine(application.policies.all(), self.request.user, self.request)
engine.for_user(self.request.user).with_request(self.request)
engine.build() engine.build()
if engine.passing: if engine.passing:
kwargs['applications'].append(application) kwargs['applications'].append(application)

View File

@ -13,8 +13,8 @@ def password_policy_checker(sender, password, **_):
setattr(sender, '__password__', password) setattr(sender, '__password__', password)
_all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order') _all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order')
for factor in _all_factors: for factor in _all_factors:
policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses()) policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses(), sender)
policy_engine.for_user(sender).build() policy_engine.build()
passing, messages = policy_engine.result passing, messages = policy_engine.result
if not passing: if not passing:
raise PasswordPolicyInvalid(*messages) raise PasswordPolicyInvalid(*messages)

View File

@ -67,8 +67,8 @@ class AuthenticationView(UserPassesTestMixin, View):
for factor in _all_factors: for factor in _all_factors:
LOGGER.debug("Checking if factor applies to user", LOGGER.debug("Checking if factor applies to user",
factor=factor, user=self.pending_user) factor=factor, user=self.pending_user)
policy_engine = PolicyEngine(factor.policies.all()) policy_engine = PolicyEngine(factor.policies.all(), self.pending_user, self.request)
policy_engine.for_user(self.pending_user).with_request(self.request).build() policy_engine.build()
if policy_engine.passing: if policy_engine.passing:
pending_factors.append((factor.uuid.hex, factor.type)) pending_factors.append((factor.uuid.hex, factor.type))
LOGGER.debug("Factor applies", factor=factor, user=self.pending_user) LOGGER.debug("Factor applies", factor=factor, user=self.pending_user)

View File

@ -31,6 +31,7 @@ class PolicyProcessInfo:
class PolicyEngine: class PolicyEngine:
"""Orchestrate policy checking, launch tasks and return result""" """Orchestrate policy checking, launch tasks and return result"""
use_cache: bool = True
policies: List[Policy] = [] policies: List[Policy] = []
__request: HttpRequest __request: HttpRequest
__user: User __user: User
@ -43,16 +44,6 @@ class PolicyEngine:
self.__user = user self.__user = user
self.__processes = [] self.__processes = []
def for_user(self, user: User) -> 'PolicyEngine':
"""Check policies for user"""
self.__user = user
return self
def with_request(self, request: HttpRequest) -> 'PolicyEngine':
"""Set request"""
self.__request = request
return self
def _select_subclasses(self) -> List[Policy]: def _select_subclasses(self) -> List[Policy]:
"""Make sure all Policies are their respective classes""" """Make sure all Policies are their respective classes"""
return Policy.objects \ return Policy.objects \
@ -69,14 +60,14 @@ class PolicyEngine:
request.http_request = self.__request request.http_request = self.__request
for policy in self._select_subclasses(): for policy in self._select_subclasses():
cached_policy = cache.get(cache_key(policy, self.__user), None) cached_policy = cache.get(cache_key(policy, self.__user), None)
if cached_policy: if cached_policy and self.use_cache:
LOGGER.debug("Taking result from cache", policy=policy) LOGGER.debug("Taking result from cache", policy=policy)
cached_policies.append(cached_policy) cached_policies.append(cached_policy)
else: else:
LOGGER.debug("Evaluating policy", policy=policy) LOGGER.debug("Evaluating policy", policy=policy)
our_end, task_end = Pipe(False) our_end, task_end = Pipe(False)
task = PolicyProcess(policy, request, task_end) task = PolicyProcess(policy, request, task_end)
LOGGER.debug("Starting Process", for_policy=policy) LOGGER.debug("Starting Process", policy=policy)
task.start() task.start()
self.__processes.append(PolicyProcessInfo(process=task, self.__processes.append(PolicyProcessInfo(process=task,
connection=our_end, policy=policy)) connection=our_end, policy=policy))

View File

@ -42,7 +42,7 @@ class PolicyProcess(Process):
if self.policy.negate: if self.policy.negate:
policy_result.passing = not policy_result.passing policy_result.passing = not policy_result.passing
LOGGER.debug("Got result", policy=self.policy, result=policy_result, LOGGER.debug("Got result", policy=self.policy, result=policy_result,
process="PolicyProcess") process="PolicyProcess", passing=policy_result.passing, user=self.request.user)
key = cache_key(self.policy, self.request.user) key = cache_key(self.policy, self.request.user)
cache.set(key, policy_result) cache.set(key, policy_result)
LOGGER.debug("Cached policy evaluation", key=key) LOGGER.debug("Cached policy evaluation", key=key)

View File

@ -18,8 +18,8 @@ def check_permissions(request, user, client):
except Application.DoesNotExist: except Application.DoesNotExist:
return redirect('passbook_providers_oauth:oauth2-permission-denied') return redirect('passbook_providers_oauth:oauth2-permission-denied')
LOGGER.debug("Checking permissions for application", user=user, application=application) LOGGER.debug("Checking permissions for application", user=user, application=application)
policy_engine = PolicyEngine(application.policies.all()) policy_engine = PolicyEngine(application.policies.all(), user, request)
policy_engine.for_user(user).with_request(request).build() policy_engine.build()
# Check permissions # Check permissions
passing, policy_messages = policy_engine.result passing, policy_messages = policy_engine.result

View File

@ -59,8 +59,9 @@ class AccessRequiredView(AccessMixin, View):
def _has_access(self): def _has_access(self):
"""Check if user has access to application""" """Check if user has access to application"""
policy_engine = PolicyEngine(self.provider.application.policies.all()) policy_engine = PolicyEngine(self.provider.application.policies.all(),
policy_engine.for_user(self.request.user).with_request(self.request).build() self.request.user, self.request)
policy_engine.build()
return policy_engine.passing return policy_engine.passing
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):

View File

@ -292,7 +292,7 @@ with CONFIG.cd('log'):
'formatters': { 'formatters': {
"plain": { "plain": {
"()": structlog.stdlib.ProcessorFormatter, "()": structlog.stdlib.ProcessorFormatter,
"processor": structlog.processors.JSONRenderer(), "processor": structlog.processors.JSONRenderer(sort_keys=True),
"foreign_pre_chain": LOG_PRE_CHAIN, "foreign_pre_chain": LOG_PRE_CHAIN,
}, },
"colored": { "colored": {

View File

@ -14,14 +14,12 @@ from structlog import get_logger
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
LOGGER = get_logger()
class WSGILogger: class WSGILogger:
""" This is the generalized WSGI middleware for any style request logging. """ """ This is the generalized WSGI middleware for any style request logging. """
def __init__(self, application): def __init__(self, application):
self.application = application self.application = application
self.logger = get_logger('passbook.wsgi')
def __healthcheck(self, start_response): def __healthcheck(self, start_response):
start_response('204 OK', []) start_response('204 OK', [])
@ -64,7 +62,7 @@ class WSGILogger:
query_string = '' query_string = ''
if environ.get('QUERY_STRING') != '': if environ.get('QUERY_STRING') != '':
query_string = f"?{environ.get('QUERY_STRING')}" query_string = f"?{environ.get('QUERY_STRING')}"
LOGGER.info(f"{environ.get('PATH_INFO', '')}{query_string}", self.logger.info(f"{environ.get('PATH_INFO', '')}{query_string}",
host=host, host=host,
method=environ.get('REQUEST_METHOD', ''), method=environ.get('REQUEST_METHOD', ''),
protocol=environ.get('SERVER_PROTOCOL', ''), protocol=environ.get('SERVER_PROTOCOL', ''),