Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions lib/Controller/ContactpersonenController.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,18 @@ public function convertToUser(string $contactpersoonId): JSONResponse
}
}

// Handle organisatie field - if it's a string UUID, convert to null to avoid validation errors
// The relationship is maintained through the organisation entity's users array
if (isset($contactData['organisatie']) && is_string($contactData['organisatie'])) {
$this->logger->info('ContactpersonenController: Converting organisatie string to null for validation', [
'originalValue' => $contactData['organisatie']
]);
$contactData['organisatie'] = null;
}
if (isset($contactData['organisation']) && is_string($contactData['organisation'])) {
$contactData['organisation'] = null;
}

$contactpersoonObject->setObject($contactData);

// Debug logging to understand data types before save
Expand All @@ -317,14 +329,10 @@ public function convertToUser(string $contactpersoonId): JSONResponse
'schemaId' => $schemaId
]);

// Save the updated contactpersoon object
$objectService->saveObject(
object: $contactpersoonObject,
register: $registerId,
schema: $schemaId,
_rbac: false,
_multitenancy: false
);
// Save using ObjectEntityMapper directly to bypass schema validation
// This avoids "Unresolved reference" errors when schema references can't be resolved
$objectMapper = $this->container->get('OCA\OpenRegister\Db\ObjectEntityMapper');
$objectMapper->update($contactpersoonObject);

$this->logger->info('ContactpersonenController: Updated contactpersoon with username', [
'contactpersoonId' => $contactpersoonId,
Expand Down
56 changes: 31 additions & 25 deletions lib/Service/OrganizationSyncService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1682,7 +1682,7 @@ private function createOrUpdateContactPersonObject(array $contactData, string $o
$this->contactpersonHandler->addUserToOrganizationEntity($contactObject, $user->getUID(), $organizationUuid);

// Update contactpersoon object owner to user UID
$this->updateContactpersoonObjectOwner($contactObject, $user->getUID(), $register, $contactSchema);
$this->updateContactpersoonObjectOwner($contactObject, $user->getUID(), $register, $contactSchema, $organizationUuid);

$this->logger->critical('🎉 USER ACCOUNT CREATED SUCCESS', [
'app' => 'softwarecatalog',
Expand Down Expand Up @@ -1815,7 +1815,7 @@ public function processSpecificContactPerson($contactObject): array
$this->contactpersonHandler->addUserToOrganizationEntity($contactObject, $user->getUID(), $organizationUuid);

// Update contactpersoon object owner to user UID.
$this->updateContactpersoonObjectOwner($contactObject, $user->getUID(), $register, $contactSchema);
$this->updateContactpersoonObjectOwner($contactObject, $user->getUID(), $register, $contactSchema, $organizationUuid);

$stats['usersCreated']++;
} else {
Expand Down Expand Up @@ -2165,22 +2165,24 @@ private function updateOrganisatieObjectOwner(object $organisatieObject, object
// Update the owner field in @self metadata to the organisation entity UUID
$selfMetadata['owner'] = $organisationEntityUuid;

// Update the organisation field in @self metadata (so organisation owns itself)
$selfMetadata['organisation'] = $organisationEntityUuid;

// Update the organisation property to the organisation entity UUID (so organisation owns itself)
$currentObject['organisation'] = $organisationEntityUuid;

// Update the object with the new @self metadata
$currentObject['@self'] = $selfMetadata;
$organisatieObject->setObject($currentObject);

// Save the updated object using ObjectService
$objectService = \OC::$server->get('OCA\OpenRegister\Service\ObjectService');
$objectService->saveObject(
object: $organisatieObject,
register: $register,
schema: $organizationSchema,
_rbac: false,
_multitenancy: false
);
// Also update the entity's owner and organisation fields directly
// These are separate from the object data and control multi-tenancy
$organisatieObject->setOwner($organisationEntityUuid);
$organisatieObject->setOrganisation($organisationEntityUuid);

// Save using ObjectEntityMapper directly to bypass validation and ensure metadata is persisted
$objectMapper = \OC::$server->get('OCA\OpenRegister\Db\ObjectEntityMapper');
$objectMapper->update($organisatieObject);

$this->logger->info('OrganizationSyncService: Successfully updated organisatie object owner and organisation', [
'organisatieId' => $organisatieId,
Expand All @@ -2207,9 +2209,10 @@ private function updateOrganisatieObjectOwner(object $organisatieObject, object
* @param string $userUID The user UID to set as owner
* @param string $register The register ID
* @param string $contactSchema The contact schema ID
* @param string|null $organizationUuidOverride Optional organization UUID to use (from caller context)
* @return void
*/
private function updateContactpersoonObjectOwner(object $contactObject, string $userUID, string $register, string $contactSchema): void
private function updateContactpersoonObjectOwner(object $contactObject, string $userUID, string $register, string $contactSchema, ?string $organizationUuidOverride = null): void
{
try {
$contactId = $contactObject->getUuid();
Expand All @@ -2218,7 +2221,8 @@ private function updateContactpersoonObjectOwner(object $contactObject, string $
'contactId' => $contactId,
'userUID' => $userUID,
'register' => $register,
'schema' => $contactSchema
'schema' => $contactSchema,
'organizationUuidOverride' => $organizationUuidOverride
]);

// Get the current object data
Expand All @@ -2231,13 +2235,14 @@ private function updateContactpersoonObjectOwner(object $contactObject, string $
$selfMetadata['owner'] = $userUID;

// Set the organisation field in @self metadata to the organization UUID
// This ensures the contact person is properly linked to their organization
$organizationUuid = $currentObject['organisation'] ?? $currentObject['organisatie'] ?? '';
// Use override if provided, otherwise try to get from object data
$organizationUuid = $organizationUuidOverride ?? $currentObject['organisation'] ?? $currentObject['organisatie'] ?? '';
if (!empty($organizationUuid)) {
$selfMetadata['organisation'] = $organizationUuid;
$this->logger->info('OrganizationSyncService: Setting @self.organisation metadata', [
'contactId' => $contactId,
'organizationUuid' => $organizationUuid
'organizationUuid' => $organizationUuid,
'source' => $organizationUuidOverride ? 'override' : 'object'
]);
} else {
$this->logger->warning('OrganizationSyncService: No organization UUID found for contact person', [
Expand All @@ -2250,15 +2255,16 @@ private function updateContactpersoonObjectOwner(object $contactObject, string $
$currentObject['@self'] = $selfMetadata;
$contactObject->setObject($currentObject);

// Save the updated object using ObjectService
$objectService = \OC::$server->get('OCA\OpenRegister\Service\ObjectService');
$objectService->saveObject(
object: $contactObject,
register: $register,
schema: $contactSchema,
_rbac: false,
_multitenancy: false
);
// Also update the entity's owner and organisation fields directly
// These are separate from the object data and control multi-tenancy
$contactObject->setOwner($userUID);
if (!empty($organizationUuid)) {
$contactObject->setOrganisation($organizationUuid);
}

// Save using ObjectEntityMapper directly to bypass validation and ensure metadata is persisted
$objectMapper = \OC::$server->get('OCA\OpenRegister\Db\ObjectEntityMapper');
$objectMapper->update($contactObject);

$this->logger->info('OrganizationSyncService: Successfully updated contactpersoon object owner and organisation', [
'contactId' => $contactId,
Expand Down
30 changes: 10 additions & 20 deletions src/modals/object/ChangeOrganisatieStatusDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,28 +127,18 @@ export default {
throw new Error('Organisatie of nieuwe status ontbreekt')
}

// Prepare the patch data - only include changed properties
const patchData = {
status: newStatus,
}

// If activating the organisation, set the owner and organisation properties
// This ensures the organisation owns itself immediately upon activation
if (newStatus.toLowerCase() === 'actief') {
const organisatieUuid = organisatie.id || organisatie.uuid || organisatie['@self']?.id

// Only set the owner and organisation properties - don't send entire @self
patchData.owner = organisatieUuid
patchData.organisation = organisatieUuid

console.info('Setting owner and organisation properties to own UUID during activation:', {
organisatieId: organisatieUuid,
ownerProperty: patchData.owner,
organisationProperty: patchData.organisation,
})
// Prepare the patch data - only include the status property
const patchData = {
status: newStatus,
}

// Update only the status (and owner/organisation if activating) using PATCH
console.info('Changing organisation status:', {
organisatieId: organisatie.id,
currentStatus: organisatie.status,
newStatus: newStatus,
})

// Update only the status using PATCH
await objectStore.patchObject('organisatie', organisatie.id, patchData)

this.success = true
Expand Down
122 changes: 122 additions & 0 deletions tests/test-organization-user-creation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,128 @@ if [ "${USER_EXISTS_AFTER}" = "ok" ]; then
fi
fi

# ---------------------------------------------------------------------------
# Step 10: Test user login via OpenRegister login endpoint
# ---------------------------------------------------------------------------
echo ""
echo -e "${YELLOW}Step 10: Testing user login via OpenRegister API...${NC}"

# First, set a known password for the user via Nextcloud OCS API (admin can do this)
# Use a complex password that passes Nextcloud's password policy
TEST_PASSWORD="T3st!P@ss_${UNIQUE_ID}"

# Use Nextcloud provisioning API to set password
SET_PW_RESPONSE=$(curl -s -X PUT \
"${NEXTCLOUD_URL}/ocs/v1.php/cloud/users/${CONTACT_EMAIL}" \
-u "${AUTH}" \
-H "OCS-APIRequest: true" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "key=password&value=${TEST_PASSWORD}")

SET_PW_STATUS=$(echo "${SET_PW_RESPONSE}" | grep -o '<status>[^<]*</status>' | sed 's/<[^>]*>//g')

if [ "${SET_PW_STATUS}" = "ok" ]; then
echo " Password set successfully for user"

# Now test login via OpenRegister endpoint
LOGIN_RESPONSE=$(curl -s -X POST \
"${NEXTCLOUD_URL}/index.php/apps/openregister/api/user/login" \
-H "Content-Type: application/json" \
-d "{\"username\": \"${CONTACT_EMAIL}\", \"password\": \"${TEST_PASSWORD}\"}")

# Check if login was successful (should return user data, not an error)
LOGIN_ERROR=$(echo "${LOGIN_RESPONSE}" | jq -r '.error // empty')
LOGIN_USER=$(echo "${LOGIN_RESPONSE}" | jq -r '.userId // .uid // .user.id // empty')

if [ -n "${LOGIN_ERROR}" ]; then
echo -e "${RED}FAILED: Login returned error: ${LOGIN_ERROR}${NC}"
elif [ -n "${LOGIN_USER}" ]; then
echo -e "${GREEN}SUCCESS: User '${CONTACT_EMAIL}' can login via OpenRegister API${NC}"
echo " Logged in user ID: ${LOGIN_USER}"
else
echo -e "${YELLOW}WARNING: Login response unclear, checking response...${NC}"
echo " Response: $(echo "${LOGIN_RESPONSE}" | jq -c '.')"
fi
else
echo -e "${YELLOW}WARNING: Could not set password for user (may require different API)${NC}"
echo " Response: ${SET_PW_RESPONSE}"
fi

# ---------------------------------------------------------------------------
# Step 11: Verify organisation object's @self metadata is updated
# ---------------------------------------------------------------------------
echo ""
echo -e "${YELLOW}Step 11: Verifying organisation object @self metadata...${NC}"

# Fetch the organisation object again to check @self metadata
ORG_OBJ_REFRESHED=$(curl -s -X GET \
"${BASE_URL}${API_PATH}/${REGISTER}/${SCHEMA}/${ORG_ID}" \
-u "${AUTH}" \
-H "Content-Type: application/json")

ORG_SELF_OWNER=$(echo "${ORG_OBJ_REFRESHED}" | jq -r '.["@self"].owner // empty')
ORG_SELF_ORGANISATION=$(echo "${ORG_OBJ_REFRESHED}" | jq -r '.["@self"].organisation // empty')
ORG_TOP_ORGANISATION=$(echo "${ORG_OBJ_REFRESHED}" | jq -r '.organisation // empty')

echo " @self.owner: ${ORG_SELF_OWNER}"
echo " @self.organisation: ${ORG_SELF_ORGANISATION}"
echo " organisation (top-level): ${ORG_TOP_ORGANISATION}"

# Verify organisation owns itself
if [ "${ORG_SELF_OWNER}" = "${ORG_ID}" ]; then
echo -e "${GREEN}SUCCESS: Organisation @self.owner is set to its own UUID (self-owned)${NC}"
else
echo -e "${RED}FAILED: Organisation @self.owner should be '${ORG_ID}' but is '${ORG_SELF_OWNER}'${NC}"
fi

if [ "${ORG_SELF_ORGANISATION}" = "${ORG_ID}" ]; then
echo -e "${GREEN}SUCCESS: Organisation @self.organisation is set to its own UUID${NC}"
else
echo -e "${YELLOW}WARNING: Organisation @self.organisation is '${ORG_SELF_ORGANISATION}' (expected '${ORG_ID}')${NC}"
fi

if [ "${ORG_TOP_ORGANISATION}" = "${ORG_ID}" ]; then
echo -e "${GREEN}SUCCESS: Organisation top-level 'organisation' field is set to its own UUID${NC}"
else
echo -e "${RED}FAILED: Organisation 'organisation' field should be '${ORG_ID}' but is '${ORG_TOP_ORGANISATION}'${NC}"
fi

# ---------------------------------------------------------------------------
# Step 12: Verify contactpersoon object's @self metadata is updated
# ---------------------------------------------------------------------------
echo ""
echo -e "${YELLOW}Step 12: Verifying contactpersoon object @self metadata...${NC}"

if [ -n "${CONTACT_UUID}" ] && [ "${CONTACT_UUID}" != "null" ]; then
# Fetch the contactpersoon object again to check @self metadata
CONTACT_OBJ_REFRESHED=$(curl -s -X GET \
"${BASE_URL}${API_PATH}/${REGISTER}/contactpersoon/${CONTACT_UUID}" \
-u "${AUTH}" \
-H "Content-Type: application/json")

CONTACT_SELF_OWNER=$(echo "${CONTACT_OBJ_REFRESHED}" | jq -r '.["@self"].owner // empty')
CONTACT_SELF_ORGANISATION=$(echo "${CONTACT_OBJ_REFRESHED}" | jq -r '.["@self"].organisation // empty')

echo " @self.owner: ${CONTACT_SELF_OWNER}"
echo " @self.organisation: ${CONTACT_SELF_ORGANISATION}"

# Verify contactpersoon is owned by the created user
if [ "${CONTACT_SELF_OWNER}" = "${CONTACT_EMAIL}" ]; then
echo -e "${GREEN}SUCCESS: Contactpersoon @self.owner is set to user '${CONTACT_EMAIL}'${NC}"
else
echo -e "${RED}FAILED: Contactpersoon @self.owner should be '${CONTACT_EMAIL}' but is '${CONTACT_SELF_OWNER}'${NC}"
fi

# Verify contactpersoon belongs to the organisation
if [ "${CONTACT_SELF_ORGANISATION}" = "${ORG_ID}" ]; then
echo -e "${GREEN}SUCCESS: Contactpersoon @self.organisation is set to organisation UUID${NC}"
else
echo -e "${RED}FAILED: Contactpersoon @self.organisation should be '${ORG_ID}' but is '${CONTACT_SELF_ORGANISATION}'${NC}"
fi
else
echo -e "${YELLOW}WARNING: Cannot verify contactpersoon metadata - UUID not found${NC}"
fi

else
echo -e "${RED}FAILED: User '${CONTACT_EMAIL}' was NOT created${NC}"
echo "Response: ${USER_CHECK_AFTER}"
Expand Down