diff --git a/.bumpversion.cfg b/.bumpversion.cfg index b4ac7ec37..2c863607c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.6.9-beta +current_version = 0.6.11-beta tag = True commit = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)\-(?P.*) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a894e196a..5f105f0a9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -96,7 +96,7 @@ build-passbook-server: before_script: - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json 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: - tags - /^version/.*$/ @@ -108,7 +108,7 @@ build-passbook-static: before_script: - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json 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: - tags - /^version/.*$/ @@ -145,8 +145,8 @@ notify-sentry: before_script: - apk add curl script: - - sentry-cli releases new passbook@0.6.9-beta - - sentry-cli releases set-commits --auto passbook@0.6.9-beta + - sentry-cli releases new passbook@0.6.11-beta + - sentry-cli releases set-commits --auto passbook@0.6.11-beta only: - tags - /^version/.*$/ diff --git a/base.Dockerfile b/base.Dockerfile index bd13d970a..ffba85149 100644 --- a/base.Dockerfile +++ b/base.Dockerfile @@ -16,5 +16,8 @@ COPY --from=locker /app/requirements-dev.txt /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 diff --git a/docker/nginx.conf b/docker/nginx.conf index d29a65a31..d914239fe 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -39,7 +39,7 @@ http { gzip on; gzip_types application/javascript image/* text/css; 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; root /data/; diff --git a/docker/uwsgi.ini b/docker/uwsgi.ini index 464aa771f..3ce917051 100644 --- a/docker/uwsgi.ini +++ b/docker/uwsgi.ini @@ -8,3 +8,4 @@ threads = 2 enable-threads = true uid = passbook gid = passbook +disable-logging=True diff --git a/helm/passbook/Chart.yaml b/helm/passbook/Chart.yaml index 7748a289b..88a0b53ce 100644 --- a/helm/passbook/Chart.yaml +++ b/helm/passbook/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v1 -appVersion: "0.6.9-beta" +appVersion: "0.6.11-beta" description: A Helm chart for 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 diff --git a/helm/passbook/requirements.yaml b/helm/passbook/requirements.yaml index b60420e4e..223f320d2 100644 --- a/helm/passbook/requirements.yaml +++ b/helm/passbook/requirements.yaml @@ -1,6 +1,6 @@ dependencies: - name: postgresql - version: 4.2.2 + version: 6.3.13 repository: https://kubernetes-charts.storage.googleapis.com/ - name: redis version: 9.2.1 diff --git a/helm/passbook/values.yaml b/helm/passbook/values.yaml index 76924b142..70f56c92f 100644 --- a/helm/passbook/values.yaml +++ b/helm/passbook/values.yaml @@ -2,7 +2,7 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. image: - tag: 0.6.9-beta + tag: 0.6.11-beta nameOverride: "" diff --git a/passbook/__init__.py b/passbook/__init__.py index a1a5aa2a1..4dba4b444 100644 --- a/passbook/__init__.py +++ b/passbook/__init__.py @@ -1,2 +1,2 @@ """passbook""" -__version__ = '0.6.9-beta' +__version__ = '0.6.11-beta' diff --git a/passbook/admin/views/policy.py b/passbook/admin/views/policy.py index f73ede3c1..264f9e073 100644 --- a/passbook/admin/views/policy.py +++ b/passbook/admin/views/policy.py @@ -122,8 +122,9 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo def form_valid(self, form): policy = self.get_object() user = form.cleaned_data.get('user') - policy_engine = PolicyEngine([policy]) - policy_engine.for_user(user).with_request(self.request).build() + policy_engine = PolicyEngine([policy], user, self.request) + policy_engine.use_cache = False + policy_engine.build() result = policy_engine.passing if result: messages.success(self.request, _('User successfully passed policy.')) diff --git a/passbook/core/templatetags/passbook_user_settings.py b/passbook/core/templatetags/passbook_user_settings.py index 18c332408..3469ab729 100644 --- a/passbook/core/templatetags/passbook_user_settings.py +++ b/passbook/core/templatetags/passbook_user_settings.py @@ -17,8 +17,8 @@ def user_factors(context: RequestContext) -> List[UserSettings]: matching_factors: List[UserSettings] = [] for factor in _all_factors: user_settings = factor.user_settings() - policy_engine = PolicyEngine(factor.policies.all()) - policy_engine.for_user(user).with_request(context.get('request')).build() + policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request')) + policy_engine.build() if policy_engine.passing and user_settings: matching_factors.append(user_settings) return matching_factors @@ -31,8 +31,8 @@ def user_sources(context: RequestContext) -> List[UserSettings]: matching_sources: List[UserSettings] = [] for factor in _all_sources: user_settings = factor.user_settings() - policy_engine = PolicyEngine(factor.policies.all()) - policy_engine.for_user(user).with_request(context.get('request')).build() + policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request')) + policy_engine.build() if policy_engine.passing and user_settings: matching_sources.append(user_settings) return matching_sources diff --git a/passbook/core/views/access.py b/passbook/core/views/access.py index fa9bad3e0..14a14d5ef 100644 --- a/passbook/core/views/access.py +++ b/passbook/core/views/access.py @@ -31,6 +31,6 @@ class AccessMixin: def user_has_access(self, application: Application, user: User) -> Tuple[bool, List[str]]: """Check if user has access to application.""" LOGGER.debug("Checking permissions", user=user, application=application) - policy_engine = PolicyEngine(application.policies.all()) - policy_engine.for_user(user).with_request(self.request).build() + policy_engine = PolicyEngine(application.policies.all(), user, self.request) + policy_engine.build() return policy_engine.result diff --git a/passbook/core/views/overview.py b/passbook/core/views/overview.py index efe37e17c..b1c62d25a 100644 --- a/passbook/core/views/overview.py +++ b/passbook/core/views/overview.py @@ -16,8 +16,7 @@ class OverviewView(LoginRequiredMixin, TemplateView): def get_context_data(self, **kwargs): kwargs['applications'] = [] for application in Application.objects.all(): - engine = PolicyEngine(application.policies.all()) - engine.for_user(self.request.user).with_request(self.request) + engine = PolicyEngine(application.policies.all(), self.request.user, self.request) engine.build() if engine.passing: kwargs['applications'].append(application) diff --git a/passbook/factors/password/signals.py b/passbook/factors/password/signals.py index cf7cea028..3a175da33 100644 --- a/passbook/factors/password/signals.py +++ b/passbook/factors/password/signals.py @@ -13,8 +13,8 @@ def password_policy_checker(sender, password, **_): setattr(sender, '__password__', password) _all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order') for factor in _all_factors: - policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses()) - policy_engine.for_user(sender).build() + policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses(), sender) + policy_engine.build() passing, messages = policy_engine.result if not passing: raise PasswordPolicyInvalid(*messages) diff --git a/passbook/factors/view.py b/passbook/factors/view.py index 062bab75e..2e06c201e 100644 --- a/passbook/factors/view.py +++ b/passbook/factors/view.py @@ -67,8 +67,8 @@ class AuthenticationView(UserPassesTestMixin, View): for factor in _all_factors: LOGGER.debug("Checking if factor applies to user", factor=factor, user=self.pending_user) - policy_engine = PolicyEngine(factor.policies.all()) - policy_engine.for_user(self.pending_user).with_request(self.request).build() + policy_engine = PolicyEngine(factor.policies.all(), self.pending_user, self.request) + policy_engine.build() if policy_engine.passing: pending_factors.append((factor.uuid.hex, factor.type)) LOGGER.debug("Factor applies", factor=factor, user=self.pending_user) diff --git a/passbook/policies/engine.py b/passbook/policies/engine.py index 813b37a32..95876b1c1 100644 --- a/passbook/policies/engine.py +++ b/passbook/policies/engine.py @@ -31,6 +31,7 @@ class PolicyProcessInfo: class PolicyEngine: """Orchestrate policy checking, launch tasks and return result""" + use_cache: bool = True policies: List[Policy] = [] __request: HttpRequest __user: User @@ -43,16 +44,6 @@ class PolicyEngine: self.__user = user 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]: """Make sure all Policies are their respective classes""" return Policy.objects \ @@ -69,14 +60,14 @@ class PolicyEngine: request.http_request = self.__request for policy in self._select_subclasses(): 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) cached_policies.append(cached_policy) else: LOGGER.debug("Evaluating policy", policy=policy) our_end, task_end = Pipe(False) task = PolicyProcess(policy, request, task_end) - LOGGER.debug("Starting Process", for_policy=policy) + LOGGER.debug("Starting Process", policy=policy) task.start() self.__processes.append(PolicyProcessInfo(process=task, connection=our_end, policy=policy)) diff --git a/passbook/policies/process.py b/passbook/policies/process.py index 5d912e8b6..14198d832 100644 --- a/passbook/policies/process.py +++ b/passbook/policies/process.py @@ -42,7 +42,7 @@ class PolicyProcess(Process): if self.policy.negate: policy_result.passing = not policy_result.passing 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) cache.set(key, policy_result) LOGGER.debug("Cached policy evaluation", key=key) diff --git a/passbook/providers/oidc/lib.py b/passbook/providers/oidc/lib.py index 057be13b5..2be1a34c2 100644 --- a/passbook/providers/oidc/lib.py +++ b/passbook/providers/oidc/lib.py @@ -18,8 +18,8 @@ def check_permissions(request, user, client): except Application.DoesNotExist: return redirect('passbook_providers_oauth:oauth2-permission-denied') LOGGER.debug("Checking permissions for application", user=user, application=application) - policy_engine = PolicyEngine(application.policies.all()) - policy_engine.for_user(user).with_request(request).build() + policy_engine = PolicyEngine(application.policies.all(), user, request) + policy_engine.build() # Check permissions passing, policy_messages = policy_engine.result diff --git a/passbook/providers/saml/views.py b/passbook/providers/saml/views.py index a7dc0bb44..8f7e12d57 100644 --- a/passbook/providers/saml/views.py +++ b/passbook/providers/saml/views.py @@ -59,8 +59,9 @@ class AccessRequiredView(AccessMixin, View): def _has_access(self): """Check if user has access to application""" - policy_engine = PolicyEngine(self.provider.application.policies.all()) - policy_engine.for_user(self.request.user).with_request(self.request).build() + policy_engine = PolicyEngine(self.provider.application.policies.all(), + self.request.user, self.request) + policy_engine.build() return policy_engine.passing def dispatch(self, request, *args, **kwargs): diff --git a/passbook/root/settings.py b/passbook/root/settings.py index a089d0d99..889df075f 100644 --- a/passbook/root/settings.py +++ b/passbook/root/settings.py @@ -292,7 +292,7 @@ with CONFIG.cd('log'): 'formatters': { "plain": { "()": structlog.stdlib.ProcessorFormatter, - "processor": structlog.processors.JSONRenderer(), + "processor": structlog.processors.JSONRenderer(sort_keys=True), "foreign_pre_chain": LOG_PRE_CHAIN, }, "colored": { diff --git a/passbook/root/wsgi.py b/passbook/root/wsgi.py index 57aedca61..848d8975f 100644 --- a/passbook/root/wsgi.py +++ b/passbook/root/wsgi.py @@ -14,14 +14,12 @@ from structlog import get_logger os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings") -LOGGER = get_logger() - - class WSGILogger: """ This is the generalized WSGI middleware for any style request logging. """ def __init__(self, application): self.application = application + self.logger = get_logger('passbook.wsgi') def __healthcheck(self, start_response): start_response('204 OK', []) @@ -64,13 +62,13 @@ class WSGILogger: query_string = '' if environ.get('QUERY_STRING') != '': query_string = f"?{environ.get('QUERY_STRING')}" - LOGGER.info(f"{environ.get('PATH_INFO', '')}{query_string}", - host=host, - method=environ.get('REQUEST_METHOD', ''), - protocol=environ.get('SERVER_PROTOCOL', ''), - status=status_code, - size=content_length / 1000 if content_length > 0 else '-', - runtime=kwargs.get('runtime')) + self.logger.info(f"{environ.get('PATH_INFO', '')}{query_string}", + host=host, + method=environ.get('REQUEST_METHOD', ''), + protocol=environ.get('SERVER_PROTOCOL', ''), + status=status_code, + size=content_length / 1000 if content_length > 0 else '-', + runtime=kwargs.get('runtime')) application = WSGILogger(get_wsgi_application())