mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
fix(web-app-suitecrm): let the external-auth provisioning path create its user
SuiteCRM 8.10.2 tightened User::hasSaveAccess() from return true to is_admin($current_user) for a record without an id, and restrictAdminOnlyFields() now forces status Inactive on such a record. CreateUser::create() runs while the browser is still anonymous, because it is the LDAP and SAML auto-create path, so every FIRST login of a directory account ends in RuntimeException: Not authorized and HTTP 500 on POST /login. The pre-existing administrator is unaffected, which is why one persona passes and one fails in both auth modes. 451f63cbdd carried the role from 8.9.1 to 8.10.2; 8.9.1 exempted a new record from both guards.
The patch raises the admin context around that one save and restores it in a finally, rather than relaxing the guards, which stay closed for every request-driven save. create() has a single caller in its own file, createExternalAuthUser, which sets is_admin 0 and external_auth_only 1 before calling, so the raised context cannot mint an administrator. patch runs with -F0 so a hunk upstream has moved fails the build instead of landing somewhere else.
The patch path is derived once in vars/main.yml and reaches the Dockerfile as a build arg, following the role's existing SUITECRM_ENTRYPOINT_SCRIPT_* shape, so the file name is written once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
db1a27b3f2
commit
495d23dbde
6 files changed
+82
No files matched your search
@@ -66,6 +66,14 @@ Role-level extensions are declared in `meta/addons/`, one file per addon:
|
||||
the Tor SOCKS proxy whenever the issuer is an onion, because a `.onion` issuer is
|
||||
unresolvable directly. `/auth` stays reachable as the local break-glass login.
|
||||
|
||||
- **Auto-created accounts** reach `User::save()` through SuiteCRM's own `CreateUser`
|
||||
while the request is still anonymous. Since 8.10.2 that save answers
|
||||
`RuntimeException: Not authorized` and forces `status = Inactive`, so
|
||||
`files/patch/external-auth-user-create.patch` raises an admin context around the save
|
||||
and restores the caller's afterwards. [`files/Dockerfile`](./files/Dockerfile) applies
|
||||
it with `patch -F0` on the unpacked release; the image build fails once the hunk stops
|
||||
matching.
|
||||
|
||||
## Persona contract opt-outs
|
||||
|
||||
[`templates/env.j2`](./templates/env.j2) seeds only the administrator into SuiteCRM (`SUITECRM_ADMIN_USERNAME` / `SUITECRM_ADMIN_PASSWORD`); `biber` reaches the app through SuiteCRM's own SAML login against Keycloak, where `SAML_AUTO_CREATE` mints the account on first assertion. In the `services.sso.enabled: false` matrix variants there is no SAML entry point and `biber` has no native account, so [`templates/playwright.env.j2`](./templates/playwright.env.j2) renders `PERSONA_BIBER_BLOCKED=true` for the generic persona journeys (the LDAP spec still signs `biber` in from the directory). The `administrator` persona keeps its native login via `ADMIN_NATIVE_PASSWORD` and runs in every variant.
|
||||
@@ -19,6 +19,7 @@ RUN rm -rf /var/lib/apt/lists/* \
|
||||
libonig-dev \
|
||||
libxml2-dev \
|
||||
libldap2-dev \
|
||||
patch \
|
||||
&& docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu \
|
||||
&& docker-php-ext-install \
|
||||
pdo \
|
||||
@@ -56,6 +57,8 @@ FROM base AS builder
|
||||
ARG SUITECRM_APP_VERSION
|
||||
ARG SUITECRM_APP_DIR
|
||||
ARG SUITECRM_LDAP_EXTENSION_DIR
|
||||
ARG SUITECRM_EXTERNAL_AUTH_PATCH_HOST_REL
|
||||
ARG SUITECRM_EXTERNAL_AUTH_PATCH_DOCKER
|
||||
|
||||
WORKDIR ${SUITECRM_APP_DIR}
|
||||
|
||||
@@ -69,6 +72,11 @@ RUN set -eux; \
|
||||
rm /tmp/suitecrm.tar.gz; \
|
||||
chmod +x bin/console
|
||||
|
||||
COPY ${SUITECRM_EXTERNAL_AUTH_PATCH_HOST_REL} ${SUITECRM_EXTERNAL_AUTH_PATCH_DOCKER}
|
||||
RUN set -eux; \
|
||||
patch -p1 -F0 -i ${SUITECRM_EXTERNAL_AUTH_PATCH_DOCKER}; \
|
||||
rm ${SUITECRM_EXTERNAL_AUTH_PATCH_DOCKER}
|
||||
|
||||
RUN set -eux; \
|
||||
composer install \
|
||||
--prefer-dist \
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
SuiteCRM 8.10.2 tightened User::hasSaveAccess() from `return true` to
|
||||
`return is_admin($current_user)` for a record without an id, and
|
||||
restrictAdminOnlyFields() now forces `status = Inactive` on such a record
|
||||
(release commit 6c7ce002a). CreateUser::create() runs while the browser is
|
||||
still anonymous, because it is the LDAP and SAML auto-create path
|
||||
(AppLdapUserProvider::createUser, AppSamlUserFactory::createUser, both via
|
||||
UserHandler::createExternalAuthUser), so every first login of a directory
|
||||
account ends in `RuntimeException: Not authorized`, HTTP 500 on POST /login
|
||||
and no session after a SAML assertion. v8.10.1 and earlier exempted a new
|
||||
record from both guards; upstream has no release and no hotfix carrying a fix.
|
||||
|
||||
\CreateUser is reachable only from that provisioning stack and from the
|
||||
installer, so the admin context is raised here instead of relaxing the guards,
|
||||
which keeps them closed for every request-driven save.
|
||||
|
||||
Generated with `diff -u` against the v8.10.2 tarball and applied with `-F0`, so
|
||||
the build fails instead of letting GNU patch's default fuzz factor land the
|
||||
hunk somewhere upstream moved it to.
|
||||
|
||||
--- a/public/legacy/include/portability/User/CreateUser.php
|
||||
+++ b/public/legacy/include/portability/User/CreateUser.php
|
||||
@@ -68,6 +68,8 @@
|
||||
*/
|
||||
public function create(string $name, array $fields): ?SugarBean
|
||||
{
|
||||
+ global $current_user;
|
||||
+
|
||||
$user = BeanFactory::newBean('Users');
|
||||
$user->user_name = $name;
|
||||
|
||||
@@ -77,7 +79,18 @@
|
||||
$user->$key = $value;
|
||||
}
|
||||
|
||||
- $user->save();
|
||||
+ $callerUser = $current_user;
|
||||
+
|
||||
+ if (!is_admin($current_user)) {
|
||||
+ $current_user = BeanFactory::newBean('Users');
|
||||
+ $current_user->is_admin = 1;
|
||||
+ }
|
||||
+
|
||||
+ try {
|
||||
+ $user->save();
|
||||
+ } finally {
|
||||
+ $current_user = $callerUser;
|
||||
+ }
|
||||
|
||||
if (empty($user)) {
|
||||
return null;
|
||||
@@ -30,6 +30,14 @@
|
||||
- compose-up
|
||||
- compose-build
|
||||
|
||||
- name: "🚀 Deploy '{{ SUITECRM_EXTERNAL_AUTH_PATCH_HOST_ABS }}'"
|
||||
copy:
|
||||
src: "{{ SUITECRM_EXTERNAL_AUTH_PATCH_SRC }}"
|
||||
dest: "{{ SUITECRM_EXTERNAL_AUTH_PATCH_HOST_ABS }}"
|
||||
notify:
|
||||
- compose-up
|
||||
- compose-build
|
||||
|
||||
- name: "🐳 Docker Compose Up for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
SUITECRM_PHPREDIS_VERSION: "{{ SUITECRM_PHPREDIS_VERSION }}"
|
||||
SUITECRM_ENTRYPOINT_SCRIPT_HOST_REL: "{{ SUITECRM_ENTRYPOINT_SCRIPT_HOST_REL }}"
|
||||
SUITECRM_ENTRYPOINT_SCRIPT_DOCKER: "{{ SUITECRM_ENTRYPOINT_SCRIPT_DOCKER }}"
|
||||
SUITECRM_EXTERNAL_AUTH_PATCH_HOST_REL: "{{ SUITECRM_EXTERNAL_AUTH_PATCH_HOST_REL }}"
|
||||
SUITECRM_EXTERNAL_AUTH_PATCH_DOCKER: "{{ SUITECRM_EXTERNAL_AUTH_PATCH_DOCKER }}"
|
||||
SUITECRM_LDAP_EXTENSION_DIR: "{{ SUITECRM_LDAP_EXTENSION_DIR }}"
|
||||
SUITECRM_APP_DIR: "{{ SUITECRM_APP_DIR }}"
|
||||
SUITECRM_PHP_MEMORY_LIMIT: "{{ SUITECRM_PHP_MEMORY_LIMIT }}"
|
||||
|
||||
@@ -44,6 +44,12 @@ SUITECRM_ENTRYPOINT_SCRIPT_HOST_ABS: "{{ [ lookup('container', application_id, '
|
||||
SUITECRM_ENTRYPOINT_SCRIPT_HOST_REL: "volumes/{{ SUITECRM_ENTRYPOINT_SCRIPT_FILE }}"
|
||||
SUITECRM_ENTRYPOINT_SCRIPT_DOCKER: "{{ [ '/usr/local/bin/', SUITECRM_ENTRYPOINT_SCRIPT_FILE ] | path_join }}"
|
||||
|
||||
SUITECRM_EXTERNAL_AUTH_PATCH_FILE: "external-auth-user-create.patch"
|
||||
SUITECRM_EXTERNAL_AUTH_PATCH_SRC: "patch/{{ SUITECRM_EXTERNAL_AUTH_PATCH_FILE }}"
|
||||
SUITECRM_EXTERNAL_AUTH_PATCH_HOST_ABS: "{{ [ lookup('container', application_id, 'directories.volumes'), SUITECRM_EXTERNAL_AUTH_PATCH_FILE ] | path_join }}"
|
||||
SUITECRM_EXTERNAL_AUTH_PATCH_HOST_REL: "volumes/{{ SUITECRM_EXTERNAL_AUTH_PATCH_FILE }}"
|
||||
SUITECRM_EXTERNAL_AUTH_PATCH_DOCKER: "{{ [ '/tmp/', SUITECRM_EXTERNAL_AUTH_PATCH_FILE ] | path_join }}"
|
||||
|
||||
SUITECRM_DESIGN_ENABLED: "{{ lookup('config', application_id, 'services.design.enabled') | bool }}"
|
||||
SUITECRM_DESIGN_LOGO_ENABLED: "{{ SUITECRM_DESIGN_ENABLED | bool and lookup('design', application_id).logo is string }}"
|
||||
SUITECRM_DESIGN_TITLE: "{{ lookup('design', application_id).title if SUITECRM_DESIGN_ENABLED | bool and lookup('design', application_id).title is string else '' }}"
|
||||
|
||||
Reference in new issue
Block a user