Skip to content

Commit 8ae7684

Browse files
Merge remote-tracking branch 'upstream/master' into feature/activity-type-no-default
2 parents cbe8322 + 81c630d commit 8ae7684

155 files changed

Lines changed: 13259 additions & 12791 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
charset = utf-8

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

.htaccess

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
21
IndexIgnore *
32

43
Options -Indexes
4+
5+
# Security headers (requires mod_headers; AllowOverride FileInfo or All).
6+
# Basic security headers.
7+
# These defaults are intentionally conservative to avoid breaking common customizations.
8+
<IfModule mod_headers.c>
9+
Header always set X-Content-Type-Options "nosniff"
10+
Header always set Referrer-Policy "strict-origin-when-cross-origin"
11+
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
12+
13+
# Prevent clickjacking on the OpenCATS UI.
14+
# Note: /careers/ intentionally unsets this header in careers/.htaccess to allow embedding Career Portal into external websites using an iframe.
15+
Header always set X-Frame-Options "SAMEORIGIN"
16+
</IfModule>

Error.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<p class="fatalError">
77
A fatal error has occurred.<br />
88
<br />
9-
<?php echo($this->errorMessage); ?>
9+
<?php $this->_($this->errorMessage); ?>
1010
</p>
1111

1212
<?php TemplateUtility::printFooter(); ?>

README-testing.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This project uses two distinct MariaDB instances during the testing phase to ens
55

66
### 1. opencatsdb (Port 3306)
77
* **Purpose:** Primary application database for functional testing.
8-
* **Data:** Pre-seeded with `test.sql` and `securityTests.sql` via Docker's `initdb.d`.
8+
* **Data:** Pre-seeded with `db/cats_schema.sql` and `test/data/securityTests.sql` via Docker's `initdb.d`.
99
* **Used By:** Manual development, Behat (Gherkin/Selenium) suites.
1010

1111
### 2. integrationtestdb (Port 3307)
@@ -29,8 +29,14 @@ To mirror the CI environment on your machine:
2929
docker compose -f docker-compose-test.yml up -d
3030
```
3131

32-
3. **Run the suites:**
33-
* **PHPUnit:** `docker exec -it opencats_test_php ./vendor/bin/phpunit src/OpenCATS/Tests/IntegrationTests`
32+
3. **Install PHP dependencies (Composer):**
33+
```bash
34+
docker run --rm -v "$(pwd)/..":/app -w /app composer:2 composer install --no-interaction --prefer-dist --ignore-platform-reqs
35+
```
36+
37+
4. **Run the suites:**
38+
* **PHPUnit Unit Tests:** `docker exec -it opencats_test_php ./vendor/bin/phpunit src/OpenCATS/Tests/UnitTests`
39+
* **PHPUnit Integration Tests:** `docker exec -it opencats_test_php ./vendor/bin/phpunit src/OpenCATS/Tests/IntegrationTests`
3440
* **Behat:** `docker exec -it opencats_test_php ./vendor/bin/behat -c ./test/behat.yml`
3541

3642
---

Security.MD

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ In order to give the community time to respond and upgrade we strongly urge you
88

99
### Password Storage
1010

11-
OpenCATS uses MD5 hashing to store passwords. This will be replaced in future versions
11+
OpenCATS hashes user passwords using PHP’s password_hash() and verifies them with password_verify(), using PASSWORD_DEFAULT (the default algorithm selected by the running PHP version).
1212

1313
### XSS
1414

15-
The main vector for [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) attacks is via the career portal (which is disabled by default). htmlspecialchars is used to protect career portal form submissions. Back-end (non-public) web-pages remain vulnerable to XSS. This internal page protection was deployed in v0.9.7.2
15+
The main vector for [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) attacks is via the career portal (which is disabled by default). htmlspecialchars is used to protect career portal form submissions. This internal page protection was deployed in v0.9.7.2. XSS hardening has been improved over time, but any custom templates and third-party modifications can reintroduce issues.
1616

1717
### Malicious uploads
1818

ajax.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
4747
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
4848

49+
/* Only start a session for POST requests so CSRF validation can determine logged-in state. */
50+
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST')
51+
{
52+
@session_name(CATS_SESSION_NAME);
53+
session_start();
54+
}
55+
4956
/* Make sure we aren't getting screwed over by magic quotes. */
5057
if (get_magic_quotes_runtime())
5158
{
@@ -60,6 +67,31 @@
6067
$_REQUEST = array_map('stripslashes', $_REQUEST);
6168
}
6269

70+
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' &&
71+
isset($_SESSION['CATS']) && $_SESSION['CATS']->isLoggedIn())
72+
{
73+
$token = null;
74+
75+
if (isset($_POST['csrfToken']))
76+
{
77+
$token = $_POST['csrfToken'];
78+
}
79+
80+
if (!$_SESSION['CATS']->isCSRFTokenValid($token))
81+
{
82+
header('Content-type: text/xml');
83+
echo '<?xml version="1.0" encoding="', AJAX_ENCODING, '"?>', "\n";
84+
echo(
85+
"<data>\n" .
86+
" <errorcode>-1</errorcode>\n" .
87+
" <errormessage>Invalid request.</errormessage>\n" .
88+
"</data>\n"
89+
);
90+
91+
die();
92+
}
93+
}
94+
6395
if (!isset($_REQUEST['f']) || empty($_REQUEST['f']))
6496
{
6597
header('Content-type: text/xml');

ajax/deleteActivity.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232

3333
$interface = new SecureAJAXInterface();
3434

35+
if ($_SERVER['REQUEST_METHOD'] !== 'POST')
36+
{
37+
$interface->outputXMLErrorPage(-1, 'Invalid request.');
38+
die();
39+
}
40+
3541
if (!$interface->isRequiredIDValid('activityID'))
3642
{
3743
$interface->outputXMLErrorPage(-1, 'Invalid activity ID.');
@@ -40,7 +46,7 @@
4046

4147
$siteID = $interface->getSiteID();
4248

43-
$activityID = $_REQUEST['activityID'];
49+
$activityID = $_POST['activityID'];
4450

4551
/* Delete the activity entry. */
4652
$activityEntries = new ActivityEntries($siteID);

ajax/editActivity.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@
3535

3636
$interface = new SecureAJAXInterface();
3737

38+
if ($_SERVER['REQUEST_METHOD'] !== 'POST')
39+
{
40+
$interface->outputXMLErrorPage(-1, 'Invalid request.');
41+
die();
42+
}
43+
3844
if (!$interface->isRequiredIDValid('activityID'))
3945
{
4046
$interface->outputXMLErrorPage(-1, 'Invalid activity ID.');
@@ -53,26 +59,30 @@
5359
die();
5460
}
5561

56-
if (!isset($_REQUEST['notes']))
62+
if (!isset($_POST['notes']))
5763
{
5864
$interface->outputXMLErrorPage(-1, 'Invalid notes.');
5965
die();
6066
}
6167

6268
$siteID = $interface->getSiteID();
6369

64-
$activityID = $_REQUEST['activityID'];
65-
$type = $_REQUEST['type'];
66-
$jobOrderID = $_REQUEST['jobOrderID'];
70+
$activityID = $_POST['activityID'];
71+
$type = $_POST['type'];
72+
$jobOrderID = $_POST['jobOrderID'];
6773

6874
/* Decode and trim the activity notes from the company. */
69-
$activityNote = trim(urldecode($_REQUEST['notes']));
70-
$activityDate = trim(urldecode($_REQUEST['date']));
71-
$activityHour = trim(urldecode($_REQUEST['hour']));
72-
$activityMinute = trim(urldecode($_REQUEST['minute']));
73-
$activityAMPM = trim(urldecode($_REQUEST['ampm']));
75+
$activityNote = trim(urldecode($_POST['notes']));
76+
$activityDate = trim(urldecode($_POST['date']));
77+
$activityHour = trim(urldecode($_POST['hour']));
78+
$activityMinute = trim(urldecode($_POST['minute']));
79+
$activityAMPM = trim(urldecode($_POST['ampm']));
80+
81+
$dateFormatFlag = $_SESSION['CATS']->isDateDMY()
82+
? DATE_FORMAT_DDMMYY
83+
: DATE_FORMAT_MMDDYY;
7484

75-
if (!DateUtility::validate('-', $activityDate, DATE_FORMAT_MMDDYY))
85+
if (!DateUtility::validate('-', $activityDate, $dateFormatFlag))
7686
{
7787
die('Invalid availability date.');
7888
return;
@@ -87,7 +97,7 @@
8797
$date = sprintf(
8898
'%s %s',
8999
DateUtility::convert(
90-
'-', $activityDate, DATE_FORMAT_MMDDYY, DATE_FORMAT_YYYYMMDD
100+
'-', $activityDate, $dateFormatFlag, DATE_FORMAT_YYYYMMDD
91101
),
92102
date('H:i:00', $time)
93103
);

ajax/getAttachmentLocal.php

Lines changed: 0 additions & 91 deletions
This file was deleted.

0 commit comments

Comments
 (0)