Allow customers to read own attributes
This commit is contained in:
@@ -10606,10 +10606,10 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Users
|
- Users
|
||||||
summary: Get customer attributes
|
summary: Get customer attributes
|
||||||
description: Get custom attributes for a customer
|
description: Get custom attributes for a customer. Authenticated customer accounts may read their own attributes without list_customer_attributes.
|
||||||
operationId: getCustomerAttributes
|
operationId: getCustomerAttributes
|
||||||
parameters:
|
parameters:
|
||||||
- name: customer_id
|
- name: customer_number
|
||||||
in: query
|
in: query
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ class customerAttributes
|
|||||||
$this->get('/customer/attributes', function () {
|
$this->get('/customer/attributes', function () {
|
||||||
// Require the user to be logged in
|
// Require the user to be logged in
|
||||||
global $response;
|
global $response;
|
||||||
$this->requirePermission('list_customer_attributes');
|
|
||||||
// Get the user object
|
// Get the user object
|
||||||
$user = (new authentication())->get_user();
|
$auth = new authentication();
|
||||||
|
$user = $auth->get_user();
|
||||||
|
$subuser = $auth->get_subuser();
|
||||||
// Check if the request was successful
|
// Check if the request was successful
|
||||||
if ($user) {
|
if ($user || $subuser) {
|
||||||
// Get the query parameters from the URL
|
// Get the query parameters from the URL
|
||||||
$data = $_GET;
|
$data = $_GET;
|
||||||
// Check if the required fields are set
|
// Check if the required fields are set
|
||||||
@@ -32,14 +33,19 @@ class customerAttributes
|
|||||||
$response->error('Customer Number must be a number', 400);
|
$response->error('Customer Number must be a number', 400);
|
||||||
}
|
}
|
||||||
// Check if the user exists
|
// Check if the user exists
|
||||||
if (!(new users_o())->automaticGetTargetUserFromRequest()->exists()) {
|
$target_user = (new users_o())->automaticGetTargetUserFromRequest();
|
||||||
|
if (!$target_user->exists()) {
|
||||||
$response->error('Customer not found', 400);
|
$response->error('Customer not found', 400);
|
||||||
}
|
}
|
||||||
|
if (!$this->canListTargetCustomerAttributes($target_user)) {
|
||||||
|
$this->requirePermission('list_customer_attributes');
|
||||||
|
}
|
||||||
// Log the incident
|
// Log the incident
|
||||||
(new logs_o())->add('customer_attributes', 'global', 1, $user->id, 'LIST_CUSTOMER_ATTRIBUTES', 'Successfully listed customer attributes');
|
$actor_id = $user !== false ? (int)$user->id : (int)($subuser->id ?? 0);
|
||||||
|
(new logs_o())->add('customer_attributes', 'global', 1, $actor_id, 'LIST_CUSTOMER_ATTRIBUTES', 'Successfully listed customer attributes');
|
||||||
// Return the list of customer notes
|
// Return the list of customer notes
|
||||||
$response->success(
|
$response->success(
|
||||||
(new users_o())->automaticGetTargetUserFromRequest()->getUserAttributes()
|
$target_user->getUserAttributes()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Log the incident
|
// Log the incident
|
||||||
@@ -49,7 +55,7 @@ class customerAttributes
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
'list_customer_attributes' => 'List all customer attributes'
|
'list_customer_attributes' => 'List all customer attributes. Authenticated customer accounts may list their own customer attributes without this permission.'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -127,4 +133,29 @@ class customerAttributes
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private function canListTargetCustomerAttributes(users_o $target_user): bool
|
||||||
|
{
|
||||||
|
if (!$target_user->exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$target_customer_number = (int)$target_user->customer_number->value();
|
||||||
|
if ($target_customer_number <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth = new authentication();
|
||||||
|
$user = $auth->get_user();
|
||||||
|
if (
|
||||||
|
$user !== false
|
||||||
|
&& $user->exists()
|
||||||
|
&& $this->hasPermission('user')
|
||||||
|
&& (int)$user->customer_number->value() === $target_customer_number
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $auth->get_subuser() !== false && $this->isOwnCustomerContext($target_customer_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
usesApiSuite();
|
||||||
|
|
||||||
|
it('lets customer booking sessions read their own customer attributes', function (): void {
|
||||||
|
api_test_covers('GET /customer/attributes', 'customer-access');
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession(['user']);
|
||||||
|
api_fixtures()->addCustomerAttribute((int)$session['user']['id'], 'onlyTankCleaning');
|
||||||
|
|
||||||
|
$response = api_client()->get(
|
||||||
|
'/customer/attributes?customer_number=' . (int)$session['user']['customer_number'],
|
||||||
|
$session['headers']
|
||||||
|
);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
|
||||||
|
$attributes = array_map(
|
||||||
|
static fn(array $attribute): string => (string)($attribute['attribute'] ?? ''),
|
||||||
|
is_array($response->data()) ? $response->data() : []
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($attributes)->toContain('onlyTankCleaning');
|
||||||
|
expect($response->body)->not->toContain('list_customer_attributes');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps customer attribute reads scoped to the authenticated customer', function (): void {
|
||||||
|
api_test_covers('GET /customer/attributes', 'customer-access');
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession(['user']);
|
||||||
|
$otherCustomer = api_fixtures()->createUser(['display_name' => 'Other Attribute Customer']);
|
||||||
|
api_fixtures()->addCustomerAttribute((int)$otherCustomer['id'], 'onlyTankCleaning');
|
||||||
|
|
||||||
|
$response = api_client()->get(
|
||||||
|
'/customer/attributes?customer_number=' . (int)$otherCustomer['customer_number'],
|
||||||
|
$session['headers']
|
||||||
|
);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['list_customer_attributes']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still lets attribute managers read another customer attributes', function (): void {
|
||||||
|
api_test_covers('GET /customer/attributes', 'permissions');
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession(['list_customer_attributes']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Managed Attribute Customer']);
|
||||||
|
api_fixtures()->addCustomerAttribute((int)$customer['id'], 'onlyTankCleaning');
|
||||||
|
|
||||||
|
$response = api_client()->get(
|
||||||
|
'/customer/attributes?customer_number=' . (int)$customer['customer_number'],
|
||||||
|
$session['headers']
|
||||||
|
);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
|
||||||
|
$attributes = array_map(
|
||||||
|
static fn(array $attribute): string => (string)($attribute['attribute'] ?? ''),
|
||||||
|
is_array($response->data()) ? $response->data() : []
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($attributes)->toContain('onlyTankCleaning');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user