Merge branch 'itflow-org:master' into balance-sheet

This commit is contained in:
Andrew Malsbury 2023-10-10 08:27:14 -05:00 committed by GitHub
commit 22a072f2b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 279 additions and 172 deletions

View File

@ -1,35 +1,22 @@
---
name: Bug report
about: Something not working quite right? Create a report to help us improve!
title: ''
labels: ''
about: Please report bugs on the Forum @ https://forum.itflow.org/t/bug
title: 'Please report bugs on the Forum'
labels: Support
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
We're now using GitHub Issues exclusively for development.
-
**Can you reproduce this on the demo at demo.itflow.org**
Yes/No/NA
Going forward, GitHub Issues will be used to track confirmed bugs & planned features via Github Projects. This allows us to keep GitHub clean & tidy, whilst maintaining an active and relaxed community experience on the Forum.
**Are you on the latest available version of ITFlow, with an up-to-date database structure?**
Yes/No
Please raise bugs on the forum @ https://forum.itflow.org/t/bug. Make sure to mention whether you can replicate the bug on demo.itflow.org.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
4. See error
Thanks,
**Expected behavior**
A clear and concise description of what you expected to happen, if not obvious.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
The ITFlow team :)
--

View File

@ -1,16 +1,25 @@
---
name: Feature request
about: Please discuss new features on the Forum @ https://forum.itflow.org/t/features
title: ''
title: 'Please discuss new features on the Forum'
labels: Support
assignees: ''
---
We're now using GitHub just to track features we're definitely planning to implement (and bugs!).
We're now using GitHub Issues exclusively for development.
-
Please discuss new feature requests on the forum @ https://forum.itflow.org/t/features. This allows us to gather interest & feedback on the features people feel are most important, whilst keeping GitHub cleaner and more about the code.
Going forward, GitHub Issues will be used to track confirmed bugs & planned features via Github Projects. This allows us to keep GitHub clean & tidy, whilst maintaining an active and relaxed community experience on the Forum.
New feature requests here will be closed.
Please discuss new feature requests on the forum @ https://forum.itflow.org/t/features. When creating discussions, try to imagine how your proposed feature would also benefit other users.
Thanks :)
All new feature requests raised here will be closed, unless agreed otherwise.
Thanks,
The ITFlow team :)
--
To privately discuss a security issue, please see https://github.com/itflow-org/itflow/security

View File

@ -1,17 +1,24 @@
---
name: Support
about: Please visit the Forum or Discord for support
title: ''
about: Please request support on the Forum @ https://forum.itflow.org/t/support
title: 'Please visit the Forum for support'
labels: Support
assignees: ''
---
Please visit the Forum or Discord for support
We're now using GitHub Issues exclusively for development.
-
Forum - https://forum.itflow.org/
Going forward, GitHub Issues will be used to track confirmed bugs & planned features via Github Projects. This allows us to keep GitHub clean & tidy, whilst maintaining an active and relaxed community experience on the Forum.
Discord - https://discord.gg/ZjCcBzTUDr
Please use the forum for support queries/issues: https://forum.itflow.org/t/support
All new support requests raised here will be closed.
Thanks,
The ITFlow team :)
--

View File

@ -16,9 +16,9 @@ jobs:
issue-message: |
Hello & Welcome! :)
Thanks for taking the time to get in touch. We'll review this issue shortly.
Thanks for taking the time to get in touch.
Whilst you're waiting, please feel free to check out the [forum](https://forum.itflow.org).
We ask that all bugs/feature/support requests are raised via the [forum](https://forum.itflow.org). We'll be in touch shortly to confirm.
pr-message: |
Hello & Welcome! :)

View File

@ -33,6 +33,8 @@
<a href="https://github.com/itflow-org/itflow/issues">Report Bug</a>
·
<a href="https://forum.itflow.org/t/features">Request Feature</a>
·
<a href="https://github.com/itflow-org/itflow/security/policy">Security</a>
</p>
</div>
@ -83,20 +85,28 @@
* FullCalendar.io
<!-- GETTING STARTED -->
## Getting Started / Installation
## Getting Started
ITFlow is self-hosted. There is a full installation guide in the [docs](https://docs.itflow.org/installation), but the main steps are:
ITFlow is self-hosted. There is a full installation guide in the [docs](https://docs.itflow.org/installation).
1. Install a LAMP stack (Linux, Apache, MariaDB, PHP)
```sh
sudo apt install git apache2 php libapache2-mod-php php-intl php-imap php-mailparse php-mysqli php-curl mariadb-server
```
2. Clone the repo
```sh
git clone https://github.com/itflow-org/itflow.git /var/www/html
```
3. Create a MariaDB Database
4. Point your browser to your HTTPS web server to begin setup
<!-- EASY INSTALL -->
### Installation via Script (Recommended Method)
**Requirements**
- Clean Install of Debian 12 or Ubuntu 22.04
- A public IP Address
- Ports 80 (HTTP) and 443 (HTTPS) TCP accessible from the outside in
- A Fully Qualified Domain Name pointing to the public IP Address example itflow.example.com, NOT itflow.xyz.example.com
**Process**
- Login as root
- Download & run install script
```
wget -O - https://github.com/itflow-org/itflow-install-script/raw/main/itflow_install.sh | bash
```
- Follow Instructions & navigate to setup URL shown
- Leave us feedback in the [forum](https://forum.itflow.org/d/11-road-map)
<!-- FEATURES -->
## Key Features

View File

@ -1,5 +1,9 @@
# Security Policy
## **Please do NOT report security concerns/vulnerabilities publicly (Github issues/forum)**
---
## In Beta
ITFlow is currently in beta and is a work in progress.
@ -13,11 +17,12 @@ We attempt to follow security best practices where possible, including [automate
| Version | Supported |
| ------- | ------------------ |
| Beta | :white_check_mark: |
| 1.0 | Yet to be released |
## Reporting a Vulnerability
**<ins>Please do not report security vulnerabilities through public GitHub issues.</ins>**
If you have discovered a security issue, please [report it](https://github.com/itflow-org/itflow/security/advisories/new) to us in as much detail as possible, so we can fix it. You should expect to receive an initial acknowledgement within 72 hours.
## Reporting a Vulnerability via GitHub Security Advisories
**Security contact: [GitHub Security Advisories](https://github.com/itflow-org/itflow/security/advisories/new)**
If you have discovered a security issue, please **[report it](https://github.com/itflow-org/itflow/security/advisories/new)** to us in as much detail as possible, so we can fix it.
You should expect to receive an initial acknowledgement within 72 hours. If you don't receive any feedback, we may have missed the initial email from GitHub (we're human!). Please raise a private forum discussion with johnny and wrongecho quoting ONLY the assigned GHSA ref.

View File

@ -45,17 +45,17 @@ if ($session_user_role == 3) {
$session_user_config_force_mfa = intval($row['user_config_force_mfa']);
$user_config_records_per_page = intval($row['user_config_records_per_page']);
$sql = mysqli_query($mysqli, "SELECT * FROM companies WHERE company_id = 1");
$sql = mysqli_query($mysqli, "SELECT * FROM companies, settings WHERE settings.company_id = companies.company_id AND companies.company_id = 1");
$row = mysqli_fetch_array($sql);
$session_company_name = $row['company_name'];
$session_company_country = $row['company_country'];
$session_company_locale = $row['company_locale'];
$session_company_currency = $row['company_currency'];
$session_company_timezone = $row['company_timezone'];
$session_timezone = $row['settings_timezone'];
// Set Timezone to the companies timezone
date_default_timezone_set('$session_company_timezone');
date_default_timezone_set('$session_timezone');
//Set Currency Format
$currency_format = numfmt_create($session_company_locale, NumberFormatter::CURRENCY);

View File

@ -96,10 +96,10 @@ if (isset($_GET['contact_id'])) {
<div><i class="fa fa-fw fa-envelope text-secondary mr-3"></i><a href='mailto:<?php echo $contact_email; ?>'><?php echo $contact_email; ?></a><button class='btn btn-sm clipboardjs' data-clipboard-text='<?php echo $contact_email; ?>'><i class='far fa-copy text-secondary'></i></button></div>
<?php }
if (!empty($contact_phone)) { ?>
<div class="mb-2"><i class="fa fa-fw fa-phone text-secondary mr-3"></i><?php echo "$contact_phone $contact_extension"; ?></div>
<div class="mb-2"><i class="fa fa-fw fa-phone text-secondary mr-3"></i><a href="tel:<?php echo "$contact_phone"?>"><?php echo "$contact_phone $contact_extension"; ?></a></div>
<?php }
if (!empty($contact_mobile)) { ?>
<div class="mb-2"><i class="fa fa-fw fa-mobile-alt text-secondary mr-3"></i><?php echo $contact_mobile; ?></div>
<div class="mb-2"><i class="fa fa-fw fa-mobile-alt text-secondary mr-3"></i><a href="tel:<?php echo $contact_mobile; ?>"><?php echo $contact_mobile; ?></a></div>
<?php }
if (!empty($contact_pin)) { ?>
<div class="mb-2"><i class="fa fa-fw fa-key text-secondary mr-3"></i><?php echo $contact_pin; ?></div>
@ -549,36 +549,36 @@ if (isset($_GET['contact_id'])) {
<?php } ?>
<script>
function updateContactNotes(contact_id) {
var notes = document.getElementById("contactNotes").value;
<script>
function updateContactNotes(contact_id) {
var notes = document.getElementById("contactNotes").value;
// Send a POST request to ajax.php as ajax.php with data contact_set_notes=true, contact_id=NUM, notes=NOTES
jQuery.post(
"ajax.php",
{
contact_set_notes: 'TRUE',
contact_id: contact_id,
notes: notes
}
)
}
</script>
// Send a POST request to ajax.php as ajax.php with data contact_set_notes=true, contact_id=NUM, notes=NOTES
jQuery.post(
"ajax.php",
{
contact_set_notes: 'TRUE',
contact_id: contact_id,
notes: notes
}
)
}
</script>
<!-- JavaScript to Show/Hide Password Form Group -->
<script>
$(document).ready(function() {
$('.authMethod').on('change', function() {
var $form = $(this).closest('.authForm');
if ($(this).val() === 'local') {
$form.find('.passwordGroup').show();
} else {
$form.find('.passwordGroup').hide();
}
<!-- JavaScript to Show/Hide Password Form Group -->
<script>
$(document).ready(function() {
$('.authMethod').on('change', function() {
var $form = $(this).closest('.authForm');
if ($(this).val() === 'local') {
$form.find('.passwordGroup').show();
} else {
$form.find('.passwordGroup').hide();
}
});
$('.authMethod').trigger('change');
});
$('.authMethod').trigger('change');
});
</script>
</script>
<?php
require_once("footer.php");

View File

@ -13,6 +13,7 @@
<input type="hidden" name="contact_important" value="0">
<input type="hidden" name="contact_billing" value="0">
<input type="hidden" name="contact_technical" value="0">
<input type="hidden" name="send_email" value="0">
<!-- End prevent undefined errors -->
<input type="hidden" name="contact_id" value="<?php echo $contact_id; ?>">
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
@ -198,7 +199,7 @@
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
</div>
<input type="password" class="form-control" data-toggle="password" name="contact_password" placeholder="Leave blank for no change" autocomplete="new-password">
<input type="password" class="form-control" data-toggle="password" name="contact_password" placeholder="Leave blank for no change" autocomplete="new-password" minlength="8">
<div class="input-group-append">
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
</div>
@ -207,7 +208,7 @@
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="send_email" value=""/>
<input type="checkbox" class="form-check-input" name="send_email" value="1"/>
<label class="form-check-label">Send user e-mail with login details?</label>
</div>
@ -252,4 +253,4 @@
</form>
</div>
</div>
</div>
</div>

View File

@ -111,14 +111,14 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
if (empty($contact_phone)) {
$contact_phone_display = "";
} else {
$contact_phone_display = "<div><i class='fas fa-fw fa-phone mr-2'></i>$contact_phone$contact_extension_display</div>";
$contact_phone_display = "<div><i class='fas fa-fw fa-phone mr-2'></i><a href='tel:$contact_phone'>$contact_phone$contact_extension_display</a></div>";
}
$contact_mobile = formatPhoneNumber($row['contact_mobile']);
if (empty($contact_mobile)) {
$contact_mobile_display = "";
} else {
$contact_mobile_display = "<div class='mt-2'><i class='fas fa-fw fa-mobile-alt mr-2'></i>$contact_mobile</div>";
$contact_mobile_display = "<div class='mt-2'><i class='fas fa-fw fa-mobile-alt mr-2'></i><a href='tel:$contact_mobile'>$contact_mobile</a></div>";
}
$contact_email = nullable_htmlentities($row['contact_email']);
if (empty($contact_email)) {
@ -210,7 +210,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</a>
<?php if ($session_user_role == 3 && $contact_primary == 0) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="post.php?anonymize_contact=<?php echo $contact_id; ?>">
<a class="dropdown-item text-danger confirm-link" href="post.php?anonymize_contact=<?php echo $contact_id; ?>">
<i class="fas fa-fw fa-user-secret mr-2"></i>Anonymize & Archive
</a>
<div class="dropdown-divider"></div>

View File

@ -72,6 +72,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$domain_webhost = intval($row['domain_webhost']);
$domain_expire = nullable_htmlentities($row['domain_expire']);
$domain_registrar_name = nullable_htmlentities($row['vendor_name']);
$domain_created_at = nullable_htmlentities($row['domain_created_at']);
if (empty($domain_registrar_name)) {
$domain_registrar_name = "-";
}
@ -82,7 +83,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
if ($row) {
$domain_webhost_name = nullable_htmlentities($row['vendor_name']);
}
$domain_created_at = nullable_htmlentities($row['domain_created_at']);
?>
<tr>

View File

@ -94,9 +94,9 @@ function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date
// Prep ticket details
$message = nl2br($message);
$message = mysqli_real_escape_string($mysqli, "<i>Email from: $contact_email at $date:-</i> <br><br>$message");
$message_escaped = mysqli_real_escape_string($mysqli, "<i>Email from: $contact_email at $date:-</i> <br><br>$message");
mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_subject = '$subject', ticket_details = '$message', ticket_priority = 'Low', ticket_status = 'Pending-Assignment', ticket_created_by = 0, ticket_contact_id = $contact_id, ticket_client_id = $client_id");
mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_subject = '$subject', ticket_details = '$message_escaped', ticket_priority = 'Low', ticket_status = 'Pending-Assignment', ticket_created_by = 0, ticket_contact_id = $contact_id, ticket_client_id = $client_id");
$id = mysqli_insert_id($mysqli);
// Logging
@ -141,27 +141,16 @@ function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date
// E-mail client notification that ticket has been created
if ($config_ticket_client_general_notifications == 1) {
$email_subject = "Ticket created - [$config_ticket_prefix$ticket_number] - $subject";
$email_body = "<i style='color: #808080'>##- Please type your reply above this line -##</i><br><br>Hello, $contact_name<br><br>Thank you for your email. A ticket regarding \"$subject\" has been automatically created for you.<br><br>Ticket: $config_ticket_prefix$ticket_number<br>Subject: $subject<br>Status: Open<br>https://$config_base_url/portal/ticket.php?id=$id<br><br>~<br>$company_name<br>Support Department<br>$config_ticket_from_email<br>$company_phone";
// Insert email into queue (first, escape vars)
$contact_email_escaped = sanitizeInput($contact_email);
$contact_name_escaped = sanitizeInput($contact_name);
$config_ticket_from_email_escaped = sanitizeInput($config_ticket_from_email);
$config_ticket_from_name_escaped = sanitizeInput($config_ticket_from_name);
$mail = sendSingleEmail(
$config_smtp_host,
$config_smtp_username,
$config_smtp_password,
$config_smtp_encryption,
$config_smtp_port,
$config_ticket_from_email,
$config_ticket_from_name,
$contact_email,
$contact_name,
$email_subject,
$email_body
);
$subject_escaped = mysqli_escape_string($mysqli, "Ticket created - [$config_ticket_prefix$ticket_number] - $subject");
$body_escaped = mysqli_escape_string($mysqli, "<i style='color: #808080'>##- Please type your reply above this line -##</i><br><br>Hello, $contact_name<br><br>Thank you for your email. A ticket regarding \"$subject\" has been automatically created for you.<br><br>Ticket: $config_ticket_prefix$ticket_number<br>Subject: $subject<br>Status: Open<br>https://$config_base_url/portal/ticket.php?id=$id<br><br>~<br>$company_name<br>Support Department<br>$config_ticket_from_email<br>$company_phone");
if ($mail !== true) {
mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Mail', notification = 'Failed to send email to $contact_email'");
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Mail', log_action = 'Error', log_description = 'Failed to send email to $contact_email regarding $subject. $mail'");
}
mysqli_query($mysqli, "INSERT INTO email_queue SET email_recipient = '$contact_email_escaped', email_recipient_name = '$contact_name_escaped', email_from = '$config_ticket_from_email_escaped', email_from_name = '$config_ticket_from_name_escaped', email_subject = '$subject_escaped', email_content = '$body_escaped'");
}
@ -173,8 +162,10 @@ function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date
$client_row = mysqli_fetch_array($client_sql);
$client_name = sanitizeInput($client_row['client_name']);
$details = removeEmoji($message);
$email_subject = "ITFlow - New Ticket - $client_name: $subject";
// TODO: Fix Emojis and HTML opening tags sometimes breaking this "forwarding"
$details = removeEmoji($message_escaped);
$email_subject = mysqli_escape_string($mysqli, "ITFlow - New Ticket - $client_name: $subject");
$email_body = "Hello, <br><br>This is a notification that a new ticket has been raised in ITFlow. <br>Client: $client_name<br>Priority: Low (email parsed)<br>Link: https://$config_base_url/ticket.php?ticket_id=$id <br><br>--------------------------------<br><br><b>$subject</b><br>$details";
mysqli_query($mysqli, "INSERT INTO email_queue SET email_recipient = '$config_ticket_new_ticket_notification_email', email_recipient_name = 'ITFlow Agents', email_from = '$config_ticket_from_email', email_from_name = '$config_ticket_from_name', email_subject = '$email_subject', email_content = '$email_body'");
@ -366,9 +357,17 @@ if ($emails) {
$date = trim(mysqli_real_escape_string($mysqli, nullable_htmlentities(strip_tags($parser->getHeader('date')))));
$attachments = $parser->getAttachments();
// Get the message content
// (first try HTML parsing, but switch to plain text if the email is empty/plain-text only)
// $message = $parser->getMessageBody('htmlEmbedded');
// if (empty($message)) {
// echo "DEBUG: Switching to plain text parsing for this message ($subject)";
// $message = $parser->getMessageBody('text');
// }
// TODO: Default to getting HTML and fallback to plaintext, but HTML emails seem to break the forward/agent notifications
$message = $parser->getMessageBody('text');
// If below is enabled and up above is enabled text based emails get cut out
//$message = $parser->getMessageBody('htmlEmbedded');
// Check if we can identify a ticket number (in square brackets)
if (preg_match("/\[$config_ticket_prefix\d+\]/", $subject, $ticket_number)) {
@ -407,14 +406,14 @@ if ($emails) {
// Couldn't match this email to an existing ticket or an existing client contact
// Checking to see if the sender domain matches a client website
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT * FROM clients WHERE client_website = '$from_domain' LIMIT 1"));
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT * FROM domains WHERE domain_name = '$from_domain' LIMIT 1"));
if ($row && $from_domain == $row['client_website']) {
if ($row && $from_domain == $row['domain_name']) {
// We found a match - create a contact under this client and raise a ticket for them
// Client details
$client_id = intval($row['client_id']);
$client_id = intval($row['domain_client_id']);
// Contact details
$password = password_hash(randomString(), PASSWORD_DEFAULT);

View File

@ -516,7 +516,7 @@ function sendSingleEmail($config_smtp_host, $config_smtp_username, $config_smtp_
$smtp_auth = true;
}
try{
try {
// Mail Server Settings
$mail->CharSet = "UTF-8"; // Specify UTF-8 charset to ensure symbols ($/£) load correctly
$mail->SMTPDebug = 0; // No Debugging
@ -712,13 +712,13 @@ function shortenClient($client) {
// Break into words.
$words = explode(' ', trim($cleaned));
$shortened = '';
// If there's only one word.
if (count($words) == 1) {
$word = $words[0];
if (strlen($word) <= 3) {
return strtoupper($word);
}
@ -753,22 +753,22 @@ function roundToNearest15($time) {
// Extract hours, minutes, and seconds from the matched time string
list(, $hours, $minutes, $seconds) = $matches;
// Convert everything to seconds for easier calculation
$totalSeconds = ($hours * 3600) + ($minutes * 60) + $seconds;
// Calculate the remainder when divided by 900 seconds (15 minutes)
$remainder = $totalSeconds % 900;
if ($remainder > 450) { // If remainder is more than 7.5 minutes (450 seconds), round up
$totalSeconds += (900 - $remainder);
} else { // Else round down
$totalSeconds -= $remainder;
}
// Convert total seconds to decimal hours
$decimalHours = $totalSeconds / 3600;
// Return the decimal hours
return number_format($decimalHours, 2);
}
}

View File

@ -50,6 +50,7 @@ $session_contact_initials = initials($session_contact_name);
$session_contact_title = sanitizeInput($contact['contact_title']);
$session_contact_email = sanitizeInput($contact['contact_email']);
$session_contact_photo = sanitizeInput($contact['contact_photo']);
$session_contact_pin = sanitizeInput($contact['contact_pin']);
$session_contact_primary = intval($contact['contact_primary']);
$session_contact_is_technical_contact = false;

View File

@ -13,6 +13,7 @@ require_once('inc_portal.php');
<p>Name: <?php echo $session_contact_name ?></p>
<p>Email: <?php echo $session_contact_email ?></p>
<p>PIN: <?php echo $session_contact_pin ?></p>
<p>Client: <?php echo $session_client_name ?></p>
<br>
<p>Client Primary Contact: <?php if ($session_contact_primary == 1) {echo "Yes"; } else {echo "No";} ?></p>
@ -35,7 +36,7 @@ require_once('inc_portal.php');
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-lock"></i></span>
</div>
<input type="password" class="form-control" minlength="6" required data-toggle="password" name="new_password" placeholder="Leave blank for no change" autocomplete="new-password">
<input type="password" class="form-control" minlength="8" required data-toggle="password" name="new_password" placeholder="Leave blank for no change" autocomplete="new-password">
</div>
</div>
<button type="submit" name="edit_profile" class="btn btn-primary text-bold mt-3"><i class="fas fa-check mr-2"></i>Save password</button>

View File

@ -17,7 +17,7 @@ if (isset($_POST['add_contact'])) {
// Set a random password
$password_hash = password_hash(randomString(), PASSWORD_DEFAULT);
}
if (!file_exists("uploads/clients/$client_id")) {
mkdir("uploads/clients/$client_id");
}
@ -68,6 +68,7 @@ if (isset($_POST['edit_contact'])) {
require_once('post/contact_model.php');
$contact_id = intval($_POST['contact_id']);
$send_email = intval($_POST['send_email']);
// Get Exisiting Contact Photo
$sql = mysqli_query($mysqli,"SELECT contact_photo FROM contacts WHERE contact_id = $contact_id");
@ -93,7 +94,7 @@ if (isset($_POST['edit_contact'])) {
}
// Send contact a welcome e-mail, if specified
if (isset($_POST['send_email']) && !empty($auth_method) && !empty($config_smtp_host)) {
if ($send_email && !empty($auth_method) && !empty($config_smtp_host)) {
// Un-sanitizied used in body of email
$contact_name = $_POST['name'];
@ -102,14 +103,18 @@ if (isset($_POST['edit_contact'])) {
$config_ticket_from_email_escaped = sanitizeInput($config_ticket_from_email);
$config_ticket_from_name_escaped = sanitizeInput($config_ticket_from_name);
// Authentication info (azure, reset password, or tech-provided temporary password)
if ($auth_method == 'azure') {
$password_info = "Login with your Microsoft (Azure AD) account.";
} elseif (empty($_POST['contact_password'])) {
$password_info = "Request a password reset at https://$config_base_url/portal/login_reset.php";
} else {
$password_info = $_POST['contact_password'];
$password_info = $_POST['contact_password'] . " -- Please change on first login";
}
$subject = sanitizeInput("Your new $session_company_name ITFlow account");
$body = mysqli_real_escape_string($mysqli, "Hello, $contact_name<br><br>An ITFlow account has been set up for you. <br><br>Username: $email <br>Password: $password_info<br><br>Login URL: https://$config_base_url/portal/<br><br>~<br>$session_company_name<br>Support Department<br>$config_ticket_from_email");
$subject = sanitizeInput("Your new $session_company_name support portal account");
$body = mysqli_real_escape_string($mysqli, "Hello, $contact_name<br><br>$session_company_name has created a support portal account for you. <br><br>Username: $email<br>Password: $password_info<br><br>Login URL: https://$config_base_url/portal/<br><br>~<br>$session_company_name<br>Support Department<br>$config_ticket_from_email");
// Queue Mail
mysqli_query($mysqli, "INSERT INTO email_queue SET email_recipient = '$email', email_recipient_name = '$name', email_from = '$config_ticket_from_email_escaped', email_from_name = '$config_ticket_from_name_escaped', email_subject = '$subject', email_content = '$body'");

View File

@ -280,3 +280,47 @@ if (isset($_POST['export_users_csv'])) {
exit;
}
if (isset($_POST['ir_reset_user_password'])) {
// Incident response: allow mass reset of agent passwords
validateAdminRole();
validateCSRFToken($_POST['csrf_token']);
// Confirm logged-in user password, for security
$admin_password = $_POST['admin_password'];
$sql = mysqli_query($mysqli, "SELECT * FROM users WHERE user_id = $session_user_id");
$userRow = mysqli_fetch_array($sql);
if (!password_verify($admin_password, $userRow['user_password'])) {
$_SESSION['alert_type'] = "error";
$_SESSION['alert_message'] = "Incorrect password.";
header("Location: " . $_SERVER["HTTP_REFERER"]);
exit;
}
// Get agents/users, other than the current user
$sql_users = mysqli_query($mysqli, "SELECT * FROM users WHERE (user_archived_at IS NULL AND user_id != $session_user_id)");
// Reset passwords
while ($row = mysqli_fetch_array($sql_users)) {
$user_id = intval($row['user_id']);
$user_email = sanitizeInput($row['user_email']);
$new_password = randomString();
$user_specific_encryption_ciphertext = encryptUserSpecificKey(trim($new_password));
echo $user_email . " -- " . $new_password; // Show
$new_password = password_hash($new_password, PASSWORD_DEFAULT);
mysqli_query($mysqli, "UPDATE users SET user_password = '$new_password', user_specific_encryption_ciphertext = '$user_specific_encryption_ciphertext' WHERE user_id = $user_id");
echo "<br><br>";
}
// Logging
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'User', log_action = 'Modify', log_description = '$session_name reset ALL user passwords', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id");
exit; // Stay on the plain text password page
}

View File

@ -12,7 +12,8 @@ require_once("inc_all_settings.php"); ?>
<form action="post.php" method="post" autocomplete="off">
<div class="form-group">
<label>Telemery</label>
<label>Telemetry</label>
<p><i>If you can't measure it, you can't improve it. Please consider turning on telemetry data to provide valuable insights on how you're using ITFlow - so we can improve it for everyone. </i></p>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-broadcast-tower"></i></span>
@ -23,12 +24,12 @@ require_once("inc_all_settings.php"); ?>
<option <?php if ($config_telemetry == "2") { echo "selected"; } ?> value = "2">Detailed</option>
</select>
</div>
<small class="form-text"><a href="https://docs.itflow.org/telemetry" target="_blank">Click Here for additional details regarding the information we gather <i class="fas fa-external-link-alt"></i></a></small>
<small class="form-text">We respect your privacy. <a href="https://docs.itflow.org/telemetry" target="_blank">Click here <i class="fas fa-external-link-alt"></i></a> for additional details regarding the information we gather. </small>
</div>
<div class="form-group">
<label>Comments</label>
<textarea class="form-control" rows="4" name="comments" placeholder="Any Comments to send before hitting Send Telemetry Data?"></textarea>
<textarea class="form-control" rows="4" name="comments" placeholder="Any one-off comments to send before hitting Send Telemetry Data?"></textarea>
</div>
<hr>

View File

@ -326,8 +326,9 @@ if (isset($_GET['ticket_id'])) {
</div>
</div>
<?php if(!empty($contact_email)){ ?>
<?php if(!empty($contact_email && $contact_email !== $session_email)){ ?>
<div class="col-md-2">
<div class="form-group">
<div class="custom-control custom-checkbox">
@ -483,13 +484,13 @@ if (isset($_GET['ticket_id'])) {
<!-- Contact card -->
<div class="card card-body card-outline card-dark mb-3">
<h4 class="text-secondary">Contact</h4>
<?php if (!empty($contact_id)) { ?>
<div>
<i class="fa fa-fw fa-user text-secondary ml-1 mr-2"></i><a href="#" data-toggle="modal" data-target="#editTicketContactModal<?php echo $ticket_id; ?>"><strong><?php echo $contact_name; ?></strong>
<i class="fa fa-fw fa-user text-secondary ml-1 mr-2"></i><a href="#" data-toggle="modal" data-target="#editTicketContactModal<?php echo $ticket_id; ?>"><strong><?php echo $contact_name; ?></strong>
</a>
</div>
</div>
<?php
@ -517,32 +518,31 @@ if (isset($_GET['ticket_id'])) {
</div>
<?php } ?>
<hr>
<?php
// Previous tickets
$prev_ticket_id = $prev_ticket_subject = $prev_ticket_status = ''; // Default blank
$sql_prev_ticket = "SELECT ticket_id, ticket_created_at, ticket_subject, ticket_status, ticket_assigned_to FROM tickets WHERE ticket_contact_id = $contact_id AND ticket_id <> $ticket_id ORDER BY ticket_id DESC LIMIT 1";
$row = mysqli_fetch_assoc(mysqli_query($mysqli, $sql_prev_ticket));
$prev_ticket_row = mysqli_fetch_assoc(mysqli_query($mysqli, $sql_prev_ticket));
$prev_ticket_id = intval($row['ticket_id']);
$prev_ticket_subject = nullable_htmlentities($row['ticket_subject']);
$prev_ticket_status = nullable_htmlentities($row['ticket_status']);
?>
if ($prev_ticket_row) {
$prev_ticket_id = intval($prev_ticket_row['ticket_id']);
$prev_ticket_subject = nullable_htmlentities($prev_ticket_row['ticket_subject']);
$prev_ticket_status = nullable_htmlentities($prev_ticket_row['ticket_status']);
?>
<div>
<i class="fa fa-fw fa-history text-secondary ml-1 mr-2"></i><b>Previous ticket:</b>
<a href="ticket.php?ticket_id=<?php echo $prev_ticket_id; ?>"><?php echo $prev_ticket_subject; ?></a>
</div>
<div class="mt-1">
<?php if ($prev_ticket_status == 'Open') { ?>
<i class="fa fa-fw fa-hourglass-start text-secondary ml-1 mr-2"></i><strong>Status:</strong>
<span class="text-danger"><?php echo $prev_ticket_status; ?></span>
<?php } else { ?>
<hr>
<div>
<i class="fa fa-fw fa-history text-secondary ml-1 mr-2"></i><b>Previous ticket:</b>
<a href="ticket.php?ticket_id=<?php echo $prev_ticket_id; ?>"><?php echo $prev_ticket_subject; ?></a>
</div>
<div class="mt-1">
<i class="fa fa-fw fa-hourglass-start text-secondary ml-1 mr-2"></i><strong>Status:</strong>
<span class="text-success"><?php echo $prev_ticket_status; ?></span>
<?php } ?>
</div>
<?php } else { ?>
<div>
<a href="#" data-toggle="modal" data-target="#editTicketContactModal<?php echo $ticket_id; ?>"><i class="fa fa-fw fa-plus mr-2"></i>Add a Contact</a>
@ -565,12 +565,12 @@ if (isset($_GET['ticket_id'])) {
$sql_ticket_watchers = mysqli_query($mysqli, "SELECT * FROM ticket_watchers WHERE watcher_ticket_id = $ticket_id ORDER BY watcher_email DESC");
while ($ticket_watcher_row = mysqli_fetch_array($sql_ticket_watchers)) {
$ticket_watcher_email = nullable_htmlentities($ticket_watcher_row['watcher_email']);
?>
?>
<div class='mt-1'>
<i class="fa fa-fw fa-eye text-secondary ml-1 mr-2"></i><?php echo $ticket_watcher_email; ?>
</div>
<?php } ?>
</div>
<!-- End Ticket watchers card -->
@ -586,14 +586,15 @@ if (isset($_GET['ticket_id'])) {
<div class="mt-2">
<i class="fa fa-fw fa-history text-secondary ml-1 mr-2"></i>Updated: <strong><?php echo $ticket_updated_at; ?></strong>
</div>
<?php
if ($ticket_status == "Closed") {
$sql_closed_by = mysqli_query($mysqli, "SELECT * FROM tickets, users WHERE ticket_closed_by = user_id");
$row = mysqli_fetch_array($sql_closed_by);
$ticket_closed_by_display = nullable_htmlentities($row['user_name']);
$ticket_closed_by_display = nullable_htmlentities($row['user_name']);
?>
<div class="mt-1">
<i class="fa fa-fw fa-user text-secondary ml-1 mr-2"></i>Closed by: <?php echo ucwords($ticket_closed_by_display); ?>
</div>
@ -601,7 +602,7 @@ if (isset($_GET['ticket_id'])) {
<i class="fa fa-fw fa-comment-dots text-secondary ml-1 mr-2"></i>Feedback: <?php echo $ticket_feedback; ?>
</div>
<?php } ?>
<?php if (!empty($ticket_total_reply_time)) { ?>
<div class="mt-1">
<i class="far fa-fw fa-clock text-secondary ml-1 mr-2"></i>Total time worked: <?php echo $ticket_total_reply_time; ?>
@ -613,13 +614,13 @@ if (isset($_GET['ticket_id'])) {
<!-- Asset card -->
<div class="card card-body card-outline card-dark mb-3">
<h4 class="text-secondary">Asset</h4>
<?php if ($asset_id == 0) { ?>
<div>
<a href="#" data-toggle="modal" data-target="#editTicketAssetModal<?php echo $ticket_id; ?>"><i class="fa fa-fw fa-plus mr-2"></i>Add an Asset</a>
</div>
<?php } else { ?>
<div>
@ -686,7 +687,7 @@ if (isset($_GET['ticket_id'])) {
<i class="fas fa-fw fa-ticket-alt"></i>
Ticket: <a href="ticket.php?ticket_id=<?php echo $service_ticket_id; ?>"><?php echo "$service_ticket_prefix$service_ticket_number" ?></a> <?php echo "on $service_ticket_created_at - <b>$service_ticket_subject</b> ($service_ticket_status)"; ?>
</p>
<?php
<?php
}
?>
</div>
@ -699,13 +700,13 @@ if (isset($_GET['ticket_id'])) {
</div>
<?php } // End Ticket asset Count ?>
<?php } // End if asset_id == 0 else ?>
</div>
<!-- End Asset card -->
<!-- Vendor card -->
<!-- Vendor card -->
<div class="card card-body card-outline card-dark mb-3">
<h4 class="text-secondary">Vendor</h4>
<?php if (empty($vendor_id)) { ?>
@ -784,14 +785,14 @@ if (isset($_GET['ticket_id'])) {
<i class="fas fa-fw fa-file-invoice mr-2"></i>Invoice Ticket
</a>
<?php }
if ($ticket_status !== "Closed") { ?>
<a href="post.php?close_ticket=<?php echo $ticket_id; ?>" class="btn btn-secondary btn-block confirm-link">
<i class="fas fa-fw fa-gavel mr-2"></i>Close Ticket
</a>
<?php } ?>
</div>
</div>
</div>

View File

@ -0,0 +1,31 @@
<div class="modal" id="resetAllUserPassModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-body">
<div class="mb-4" style="text-align: center;">
<i class="far fas fa-10x fa-skull-crossbones text-danger mb-3 mt-3"></i>
<h2>Incident Response: Agent Password Reset</h2>
<br>
<div class="alert alert-danger" role="alert">
<b>This is a potentially destructive function.<br>It is intended to be used as part of a potential security incident.</b>
</div>
<h6 class="mb-4 text-secondary"><b>All ITFlow agent passwords will be reset and shown to you </b><i>(except yours - change yours first!)</i>.<br/><br/>You should communicate temporary passwords to agents out of band (e.g. via a phone call) and require they are changed ASAP.</h6>
<form action="post.php" method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<div class="row col-7 offset-4">
<div class="input-group">
<div class="input-group-prepend">
<input type="password" class="form-control" placeholder="Enter your account password to continue" name="admin_password" autocomplete="new-password" required>
</div>
</div>
</div>
<br>
<button class="btn btn-danger" type="submit" name="ir_reset_user_password"><i class="fas fa-fw fa-key mr-2"></i>Reset passwords</button>
</form>
</div>
<button type="button" class="btn btn-outline-secondary btn-lg px-5 mr-4" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>

View File

@ -33,6 +33,10 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown"></button>
<div class="dropdown-menu">
<!--<a class="dropdown-item text-dark" href="#" data-toggle="modal" data-target="#userInviteModal"><i class="fas fa-paper-plane mr-2"></i>Invite User</a>-->
<?php if ($num_rows[0] > 1) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="#" data-toggle="modal" data-target="#resetAllUserPassModal"><i class="fas fa-skull-crossbones mr-2"></i>IR</a>
<?php } ?>
</div>
</div>
</div>
@ -197,4 +201,5 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
require_once("user_add_modal.php");
require_once("user_invite_modal.php");
require_once("user_export_modal.php");
require_once("user_all_reset_password_modal.php");
require_once("footer.php");