Merge branch 'develop' of github.com:cerebrate-project/cerebrate into refactor-metatemplates
commit
324ac1ce40
|
@ -0,0 +1,52 @@
|
|||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
tags: [ 'v*.*' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: docker/Dockerfile
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
"COMPOSER_VERSION=2.1.5"
|
||||
"PHP_VERSION=7.4"
|
||||
"DEBIAN_RELEASE=buster"
|
|
@ -7,3 +7,7 @@ vendor
|
|||
webroot/theme/node_modules
|
||||
webroot/scss/*.css
|
||||
.vscode
|
||||
docker/run/
|
||||
.phpunit.result.cache
|
||||
config.json
|
||||
phpunit.xml
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
## Requirements
|
||||
|
||||
An Ubuntu server (18.04/20.04 should both work fine) - though other linux installations should work too.
|
||||
- apache2, mysql/mariadb, sqlite need to be installed and running
|
||||
- apache2 (or nginx), mysql/mariadb, sqlite need to be installed and running
|
||||
- php extensions for intl, mysql, sqlite3, mbstring, xml need to be installed and running
|
||||
- php extention for curl (not required but makes composer run a little faster)
|
||||
- composer
|
||||
|
||||
## Network requirements
|
||||
|
@ -17,8 +18,16 @@ Cerebrate communicates via HTTPS so in order to be able to connect to other cere
|
|||
## Cerebrate installation instructions
|
||||
|
||||
It should be sufficient to issue the following command to install the dependencies:
|
||||
|
||||
- for apache
|
||||
|
||||
```bash
|
||||
sudo apt install apache2 mariadb-server git composer php-intl php-mbstring php-dom php-xml unzip php-ldap php-sqlite3 sqlite libapache2-mod-php php-mysql
|
||||
sudo apt install apache2 mariadb-server git composer php-intl php-mbstring php-dom php-xml unzip php-ldap php-sqlite3 php-curl sqlite libapache2-mod-php php-mysql
|
||||
```
|
||||
|
||||
- for nginx
|
||||
```bash
|
||||
sudo apt install nginx mariadb-server git composer php-intl php-mbstring php-dom php-xml unzip php-ldap php-sqlite3 sqlite php-fpm php-curl php-mysql
|
||||
```
|
||||
|
||||
Clone this repository (for example into /var/www/cerebrate)
|
||||
|
@ -32,12 +41,19 @@ sudo -u www-data git clone https://github.com/cerebrate-project/cerebrate.git /v
|
|||
Run composer
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/.composer
|
||||
sudo chown www-data:www-data /var/www/.composer
|
||||
cd /var/www/cerebrate
|
||||
sudo -u www-data composer install
|
||||
sudo -H -u www-data composer install
|
||||
```
|
||||
|
||||
Create a database for cerebrate
|
||||
|
||||
With a fresh install of Ubuntu sudo to the (system) root user before logging in as the mysql root
|
||||
```Bash
|
||||
sudo -i mysql -u root
|
||||
```
|
||||
|
||||
From SQL shell:
|
||||
```mysql
|
||||
mysql
|
||||
|
@ -46,6 +62,7 @@ CREATE USER 'cerebrate'@'localhost' IDENTIFIED BY 'YOUR_PASSWORD';
|
|||
GRANT USAGE ON *.* to cerebrate@localhost;
|
||||
GRANT ALL PRIVILEGES ON cerebrate.* to cerebrate@localhost;
|
||||
FLUSH PRIVILEGES;
|
||||
QUIT;
|
||||
```
|
||||
|
||||
Or from Bash:
|
||||
|
@ -57,12 +74,6 @@ sudo mysql -e "GRANT ALL PRIVILEGES ON cerebrate.* to cerebrate@localhost;"
|
|||
sudo mysql -e "FLUSH PRIVILEGES;"
|
||||
```
|
||||
|
||||
Load the default table structure into the database
|
||||
|
||||
```bash
|
||||
sudo mysql -u cerebrate -p cerebrate < /var/www/cerebrate/INSTALL/mysql.sql
|
||||
```
|
||||
|
||||
create your local configuration and set the db credentials
|
||||
|
||||
```bash
|
||||
|
@ -71,7 +82,7 @@ sudo -u www-data cp -a /var/www/cerebrate/config/config.example.json /var/www/ce
|
|||
sudo -u www-data vim /var/www/cerebrate/config/app_local.php
|
||||
```
|
||||
|
||||
mod_rewrite needs to be enabled:
|
||||
mod_rewrite needs to be enabled if __using apache__:
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
|
@ -91,9 +102,9 @@ This would be, when following the steps above:
|
|||
|
||||
Run the database schema migrations
|
||||
```bash
|
||||
/var/www/cerebrate/bin/cake migrations migrate
|
||||
/var/www/cerebrate/bin/cake migrations migrate -p tags
|
||||
/var/www/cerebrate/bin/cake migrations migrate -p ADmad/SocialAuth
|
||||
sudo -u www-data /var/www/cerebrate/bin/cake migrations migrate
|
||||
sudo -u www-data /var/www/cerebrate/bin/cake migrations migrate -p tags
|
||||
sudo -u www-data /var/www/cerebrate/bin/cake migrations migrate -p ADmad/SocialAuth
|
||||
```
|
||||
|
||||
Clean cakephp caches
|
||||
|
@ -104,16 +115,31 @@ sudo rm /var/www/cerebrate/tmp/cache/persistent/*
|
|||
|
||||
Create an apache config file for cerebrate / ssh key and point the document root to /var/www/cerebrate/webroot and you're good to go
|
||||
|
||||
For development installs the following can be done:
|
||||
For development installs the following can be done for either apache or nginx:
|
||||
|
||||
```bash
|
||||
# Apache
|
||||
# This configuration is purely meant for local installations for development / testing
|
||||
# Using HTTP on an unhardened apache is by no means meant to be used in any production environment
|
||||
sudo cp /var/www/cerebrate/INSTALL/cerebrate_dev.conf /etc/apache2/sites-available/
|
||||
sudo ln -s /etc/apache2/sites-available/cerebrate_dev.conf /etc/apache2/sites-enabled/
|
||||
sudo cp /var/www/cerebrate/INSTALL/cerebrate_apache_dev.conf /etc/apache2/sites-available/
|
||||
sudo ln -s /etc/apache2/sites-available/cerebrate_apache_dev.conf /etc/apache2/sites-enabled/
|
||||
sudo service apache2 restart
|
||||
```
|
||||
|
||||
OR
|
||||
|
||||
```bash
|
||||
# NGINX
|
||||
# This configuration is purely meant for local installations for development / testing
|
||||
# Using HTTP on an unhardened apache is by no means meant to be used in any production environment
|
||||
sudo cp /var/www/cerebrate/INSTALL/cerebrate_nginx.conf /etc/nginx/sites-available/
|
||||
sudo ln -s /etc/nginx/sites-available/cerebrate_nginx.conf /etc/nginx/sites-enabled/
|
||||
sudo systemctl disable apache2 # may be required if apache is using port
|
||||
sudo service nginx restart
|
||||
sudo systemctl enable nginx
|
||||
|
||||
```
|
||||
|
||||
Now you can point your browser to: http://localhost:8000
|
||||
|
||||
To log in use the default credentials below:
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
## Cerebrate Nginx Web Server Configuration
|
||||
server {
|
||||
listen 8000;
|
||||
# listen 443 ssl;
|
||||
|
||||
root /var/www/cerebrate/webroot;
|
||||
error_log /var/log/nginx/cerebrate_error.log;
|
||||
access_log /var/log/nginx/cerebrate_access.log;
|
||||
|
||||
# Add index.php to the list if you are using PHP
|
||||
index index.html index.htm index.nginx-debian.html index.php;
|
||||
|
||||
server_name _;
|
||||
|
||||
# Configure Crypto Keys/Certificates/DH
|
||||
# If enabling this setting change port above, should also set the server name
|
||||
# ssl_certificate /path/to/ssl/cert;
|
||||
# ssl_certificate_key /path/to/ssl/cert;
|
||||
|
||||
# enable HSTS
|
||||
# add_header Strict-Transport-Security "max-age=15768000; includeSubdomains";
|
||||
# add_header X-Frame-Options SAMEORIGIN;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
}
|
||||
}
|
|
@ -1,402 +0,0 @@
|
|||
-- MySQL dump 10.16 Distrib 10.1.44-MariaDB, for debian-linux-gnu (x86_64)
|
||||
--
|
||||
-- Host: localhost Database: cerebrate
|
||||
-- ------------------------------------------------------
|
||||
-- Server version 10.1.44-MariaDB-0ubuntu0.18.04.1
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
|
||||
--
|
||||
-- Table structure for table `alignment_tags`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `alignment_tags`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `alignment_tags` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`alignment_id` int(10) unsigned NOT NULL,
|
||||
`tag_id` int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `alignment_id` (`alignment_id`),
|
||||
KEY `tag_id` (`tag_id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_1` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_10` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_11` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_12` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_3` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_4` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_5` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_6` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_7` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_8` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`),
|
||||
CONSTRAINT `alignment_tags_ibfk_9` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `alignments`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `alignments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `alignments` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`individual_id` int(10) unsigned NOT NULL,
|
||||
`organisation_id` int(10) unsigned NOT NULL,
|
||||
`type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT 'member',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `individual_id` (`individual_id`),
|
||||
KEY `organisation_id` (`organisation_id`),
|
||||
CONSTRAINT `alignments_ibfk_1` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`),
|
||||
CONSTRAINT `alignments_ibfk_2` FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `auth_keys`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `auth_keys`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `auth_keys` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`authkey` varchar(72) CHARACTER SET ascii DEFAULT NULL,
|
||||
`authkey_start` varchar(4) CHARACTER SET ascii DEFAULT NULL,
|
||||
`authkey_end` varchar(4) CHARACTER SET ascii DEFAULT NULL,
|
||||
`created` int(10) unsigned NOT NULL,
|
||||
`expiration` int(10) unsigned NOT NULL,
|
||||
`user_id` int(10) unsigned NOT NULL,
|
||||
`comment` text COLLATE utf8mb4_unicode_ci,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `authkey_start` (`authkey_start`),
|
||||
KEY `authkey_end` (`authkey_end`),
|
||||
KEY `created` (`created`),
|
||||
KEY `expiration` (`expiration`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `broods`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `broods`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `broods` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`url` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`organisation_id` int(10) unsigned NOT NULL,
|
||||
`trusted` tinyint(1) DEFAULT NULL,
|
||||
`pull` tinyint(1) DEFAULT NULL,
|
||||
`skip_proxy` tinyint(1) DEFAULT NULL,
|
||||
`authkey` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uuid` (`uuid`),
|
||||
KEY `name` (`name`),
|
||||
KEY `url` (`url`),
|
||||
KEY `authkey` (`authkey`),
|
||||
KEY `organisation_id` (`organisation_id`),
|
||||
FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `encryption_keys`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `encryption_keys`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `encryption_keys` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
`type` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`encryption_key` text COLLATE utf8mb4_unicode_ci,
|
||||
`revoked` tinyint(1) DEFAULT NULL,
|
||||
`expires` int(10) unsigned DEFAULT NULL,
|
||||
`owner_id` int(10) unsigned DEFAULT NULL,
|
||||
`owner_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uuid` (`uuid`),
|
||||
KEY `type` (`type`),
|
||||
KEY `expires` (`expires`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `individual_encryption_keys`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `individual_encryption_keys`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `individual_encryption_keys` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`individual_id` int(10) unsigned NOT NULL,
|
||||
`encryption_key_id` int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `individual_id` (`individual_id`),
|
||||
KEY `encryption_key_id` (`encryption_key_id`),
|
||||
CONSTRAINT `individual_encryption_keys_ibfk_1` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`),
|
||||
CONSTRAINT `individual_encryption_keys_ibfk_2` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`),
|
||||
CONSTRAINT `individual_encryption_keys_ibfk_3` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`),
|
||||
CONSTRAINT `individual_encryption_keys_ibfk_4` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`),
|
||||
CONSTRAINT `individual_encryption_keys_ibfk_5` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`),
|
||||
CONSTRAINT `individual_encryption_keys_ibfk_6` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `individuals`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `individuals`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `individuals` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
`email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`first_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`last_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`position` text COLLATE utf8mb4_unicode_ci,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uuid` (`uuid`),
|
||||
KEY `email` (`email`),
|
||||
KEY `first_name` (`first_name`),
|
||||
KEY `last_name` (`last_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `local_tools`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `local_tools`;
|
||||
CREATE TABLE `local_tools` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`connector` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`settings` text COLLATE utf8mb4_unicode_ci,
|
||||
`exposed` tinyint(1) NOT NULL,
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `name` (`name`),
|
||||
KEY `connector` (`connector`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
--
|
||||
-- Table structure for table `organisation_encryption_keys`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `organisation_encryption_keys`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `organisation_encryption_keys` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`organisation_id` int(10) unsigned NOT NULL,
|
||||
`encryption_key_id` int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `organisation_id` (`organisation_id`),
|
||||
KEY `encryption_key_id` (`encryption_key_id`),
|
||||
CONSTRAINT `organisation_encryption_keys_ibfk_1` FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`),
|
||||
CONSTRAINT `organisation_encryption_keys_ibfk_2` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `organisations`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `organisations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `organisations` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`url` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`nationality` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`sector` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`contacts` text COLLATE utf8mb4_unicode_ci,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uuid` (`uuid`),
|
||||
KEY `name` (`name`),
|
||||
KEY `url` (`url`),
|
||||
KEY `nationality` (`nationality`),
|
||||
KEY `sector` (`sector`),
|
||||
KEY `type` (`type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `roles`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `roles` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`is_default` tinyint(1) DEFAULT NULL,
|
||||
`perm_admin` tinyint(1) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `name` (`name`),
|
||||
KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `tags`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tags` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`colour` varchar(6) CHARACTER SET ascii NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `name` (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
`username` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`password` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`role_id` int(11) unsigned NOT NULL,
|
||||
`individual_id` int(11) unsigned NOT NULL,
|
||||
`disabled` tinyint(1) DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uuid` (`uuid`),
|
||||
KEY `role_id` (`role_id`),
|
||||
KEY `individual_id` (`individual_id`),
|
||||
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`),
|
||||
CONSTRAINT `users_ibfk_2` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
CREATE TABLE `sharing_groups` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`releasability` text DEFAULT NULL,
|
||||
`description` text DEFAULT NULL,
|
||||
`organisation_id` int(10) unsigned NOT NULL,
|
||||
`user_id` int(10) unsigned NOT NULL,
|
||||
`active` tinyint(1) DEFAULT '1',
|
||||
`local` tinyint(1) DEFAULT '1',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uuid` (`uuid`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `organisation_id` (`organisation_id`),
|
||||
KEY `name` (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `sgo` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`sharing_group_id` int(10) unsigned NOT NULL,
|
||||
`organisation_id` int(10) unsigned NOT NULL,
|
||||
`deleted` tinyint(1) DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `sharing_group_id` (`sharing_group_id`),
|
||||
KEY `organisation_id` (`organisation_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `meta_fields` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`scope` varchar(191) NOT NULL,
|
||||
`parent_id` int(10) unsigned NOT NULL,
|
||||
`field` varchar(191) NOT NULL,
|
||||
`value` varchar(191) NOT NULL,
|
||||
`uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL,
|
||||
`meta_template_id` int(10) unsigned NOT NULL,
|
||||
`meta_template_field_id` int(10) unsigned NOT NULL,
|
||||
`is_default` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `scope` (`scope`),
|
||||
KEY `uuid` (`uuid`),
|
||||
KEY `parent_id` (`parent_id`),
|
||||
KEY `field` (`field`),
|
||||
KEY `value` (`value`),
|
||||
KEY `meta_template_id` (`meta_template_id`),
|
||||
KEY `meta_template_field_id` (`meta_template_field_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `meta_templates` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`scope` varchar(191) NOT NULL,
|
||||
`name` varchar(191) NOT NULL,
|
||||
`namespace` varchar(191) NOT NULL,
|
||||
`description` text,
|
||||
`version` varchar(191) NOT NULL,
|
||||
`uuid` varchar(40) CHARACTER SET ascii,
|
||||
`source` varchar(191),
|
||||
`enabled` tinyint(1) DEFAULT 0,
|
||||
`is_default` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `scope` (`scope`),
|
||||
KEY `source` (`source`),
|
||||
KEY `name` (`name`),
|
||||
KEY `namespace` (`namespace`),
|
||||
KEY `version` (`version`),
|
||||
KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `meta_template_fields` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`field` varchar(191) NOT NULL,
|
||||
`type` varchar(191) NOT NULL,
|
||||
`meta_template_id` int(10) unsigned NOT NULL,
|
||||
`regex` text,
|
||||
`multiple` tinyint(1) DEFAULT 0,
|
||||
`enabled` tinyint(1) DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `meta_template_id` FOREIGN KEY (`meta_template_id`) REFERENCES `meta_templates` (`id`),
|
||||
KEY `field` (`field`),
|
||||
KEY `type` (`type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
-- Dump completed on 2020-06-22 14:30:02
|
30
README.md
30
README.md
|
@ -1,24 +1,31 @@
|
|||
# cerebrate
|
||||
|
||||
The Cerebrate Sync Platform core software. Cerebrate is an open-source platform meant to act as a trusted contact information provider and interconnection orchestrator for other security tools.
|
||||
Cerebrate is an [open-source platform](https://github.com/cerebrate-project) meant to act as a trusted contact information provider and interconnection orchestrator for other security tools (such as [MISP](https://www.misp-project.org/)).
|
||||
|
||||
It is currently being built under the MeliCERTes v2 project and is heavily work in progress.
|
||||
# Features
|
||||
|
||||
# Current features
|
||||
- Advanced repository to manage individuals and organisations;
|
||||
- Key store for public encryption and signing cryptographic keys (e.g. PGP);
|
||||
- Distributed synchronisation model where multiple Cerebrate instances can be interconnected amongst organisations and/or departments;
|
||||
- Management of individuals and their affiliations to each organisations;
|
||||
- Advanced API and CLI to integrate with existing tools (e.g. importing existing directory information);
|
||||
- Dynamic model for creating new organisational structures;
|
||||
- Support existing organisational structures such as [FIRST.org](https://www.first.org/) directory, EU [CSIRTs network](https://csirtsnetwork.eu/);
|
||||
- Local tooling interconnection to easily connect existing tools with their native protocols;
|
||||
|
||||
- Repository of organisations and individuals
|
||||
- Maintain signing and encryption keys
|
||||
- Maintain affiliations between organisations and individuals
|
||||
Cerebrate is developed in the scope of the MeliCERTes v2 project.
|
||||
|
||||
## Screenshots
|
||||
|
||||
![Dashboard](https://www.cerebrate-project.org/assets/images/screenshots/Screenshot%20from%202021-10-19%2016-31-56.png)
|
||||
|
||||
List of individuals along with their affiliations
|
||||
|
||||
![List of individuals](/documentation/images/individuals.png)
|
||||
![List of individuals](https://www.cerebrate-project.org/assets/images/screenshots/Screenshot%20from%202021-10-19%2016-32-35.png)
|
||||
|
||||
Adding organisations
|
||||
|
||||
![Adding an organisation](/documentation/images/add_org.png)
|
||||
![Adding an organisation](https://www.cerebrate-project.org/assets/images/screenshots/Screenshot%20from%202021-10-19%2016-33-04.png)
|
||||
|
||||
Everything is available via the API, here an example of a search query for all international organisations in the DB.
|
||||
|
||||
|
@ -28,6 +35,10 @@ Managing public keys and assigning them to users both for communication and vali
|
|||
|
||||
![Encryption key management](/documentation/images/add_encryption_key.png)
|
||||
|
||||
Dynamic model for creating new organisation structre
|
||||
|
||||
![Meta Field Templates](https://www.cerebrate-project.org/assets/images/screenshots/Screenshot%20from%202021-10-19%2016-38-21.png)
|
||||
|
||||
# Requirements and installation
|
||||
|
||||
The platform is built on CakePHP 4 along with Bootstrap 4 and shares parts of the code-base with [MISP](https://www.github.com/MISP).
|
||||
|
@ -45,6 +56,7 @@ For installation via docker, refer to the [cerebrate-docker](https://github.com/
|
|||
~~~~
|
||||
The software is released under the AGPLv3.
|
||||
|
||||
Copyright (C) 2019, 2020 Andras Iklody
|
||||
Copyright (C) 2019, 2021 Andras Iklody
|
||||
Copyright (C) 2020-2021 Sami Mokaddem
|
||||
Copyright (C) CIRCL - Computer Incident Response Center Luxembourg
|
||||
~~~~
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"admad/cakephp-social-auth": "^1.1",
|
||||
"cakephp/authentication": "^2.0",
|
||||
"cakephp/authorization": "^2.0",
|
||||
"cakephp/cakephp": "^4.0",
|
||||
"cakephp/cakephp": "^4.3",
|
||||
"cakephp/migrations": "^3.0",
|
||||
"cakephp/plugin-installer": "^1.2",
|
||||
"erusev/parsedown": "^1.7",
|
||||
|
@ -19,9 +19,12 @@
|
|||
"cakephp/bake": "^2.0.3",
|
||||
"cakephp/cakephp-codesniffer": "~4.0.0",
|
||||
"cakephp/debug_kit": "^4.0",
|
||||
"fzaninotto/faker": "^1.9",
|
||||
"josegonzalez/dotenv": "^3.2",
|
||||
"league/openapi-psr7-validator": "^0.16.4",
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"psy/psysh": "@stable"
|
||||
"psy/psysh": "@stable",
|
||||
"wiremock-php/wiremock-php": "^2.32"
|
||||
},
|
||||
"suggest": {
|
||||
"markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.",
|
||||
|
@ -44,7 +47,6 @@
|
|||
"scripts": {
|
||||
"post-install-cmd": "App\\Console\\Installer::postInstall",
|
||||
"post-create-project-cmd": "App\\Console\\Installer::postInstall",
|
||||
"post-autoload-dump": "Cake\\Composer\\Installer\\PluginInstaller::postAutoloadDump",
|
||||
"check": [
|
||||
"@test",
|
||||
"@cs-check"
|
||||
|
@ -52,7 +54,11 @@
|
|||
"cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
|
||||
"cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
|
||||
"stan": "phpstan analyse src/",
|
||||
"test": "phpunit --colors=always"
|
||||
"test": [
|
||||
"sh ./tests/Helper/wiremock/start.sh",
|
||||
"phpunit",
|
||||
"sh ./tests/Helper/wiremock/stop.sh"
|
||||
]
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
final class AuditLogs extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* Write your reversible migrations using this method.
|
||||
*
|
||||
* More information on writing migrations is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
*
|
||||
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||
* with the Table class.
|
||||
*/
|
||||
|
||||
public $autoId = false; // turn off automatic `id` column create. We want it to be `int(10) unsigned`
|
||||
|
||||
public function change(): void
|
||||
{
|
||||
$exists = $this->hasTable('audit_logs');
|
||||
if (!$exists) {
|
||||
$table = $this->table('audit_logs', [
|
||||
'signed' => false,
|
||||
'collation' => 'utf8mb4_unicode_ci'
|
||||
]);
|
||||
$table
|
||||
->addColumn('id', 'integer', [
|
||||
'autoIncrement' => true,
|
||||
'limit' => 10,
|
||||
'signed' => false,
|
||||
])
|
||||
->addPrimaryKey('id')
|
||||
->addColumn('user_id', 'integer', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'signed' => false,
|
||||
'length' => 10
|
||||
])
|
||||
->addColumn('authkey_id', 'integer', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'signed' => false,
|
||||
'length' => 10
|
||||
])
|
||||
->addColumn('request_ip', 'varbinary', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'length' => 16
|
||||
])
|
||||
->addColumn('request_type', 'boolean', [
|
||||
'null' => false
|
||||
])
|
||||
->addColumn('request_id', 'integer', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'signed' => false,
|
||||
'length' => 10
|
||||
])
|
||||
->addColumn('request_action', 'string', [
|
||||
'null' => false,
|
||||
'length' => 20
|
||||
])
|
||||
->addColumn('model', 'string', [
|
||||
'null' => false,
|
||||
'length' => 80
|
||||
])
|
||||
->addColumn('model_id', 'integer', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'signed' => false,
|
||||
'length' => 10
|
||||
])
|
||||
->addColumn('model_title', 'text', [
|
||||
'default' => null,
|
||||
'null' => true
|
||||
])
|
||||
->addColumn('change', 'blob', [
|
||||
])
|
||||
->addColumn('created', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
])
|
||||
->addIndex('user_id')
|
||||
->addIndex('request_ip')
|
||||
->addIndex('model')
|
||||
->addIndex('model_id')
|
||||
->addIndex('request_action')
|
||||
->addIndex('created');
|
||||
$table->create();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
final class UserOrg extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* Write your reversible migrations using this method.
|
||||
*
|
||||
* More information on writing migrations is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
*
|
||||
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||
* with the Table class.
|
||||
*/
|
||||
public function change(): void
|
||||
{
|
||||
$exists = $this->table('users')->hasColumn('organisation_id');
|
||||
if (!$exists) {
|
||||
$this->table('users')
|
||||
->addColumn('organisation_id', 'integer', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'signed' => false,
|
||||
'length' => 10
|
||||
])
|
||||
->addIndex('organisation_id')
|
||||
->update();
|
||||
}
|
||||
$q1 = $this->getQueryBuilder();
|
||||
$org_id = $q1->select(['min(id)'])->from('organisations')->execute()->fetchAll()[0][0];
|
||||
if (!empty($org_id)) {
|
||||
$q2 = $this->getQueryBuilder();
|
||||
$q2->update('users')
|
||||
->set('organisation_id', $org_id)
|
||||
->where(['organisation_id IS NULL'])
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
final class AuditChanged extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* Write your reversible migrations using this method.
|
||||
*
|
||||
* More information on writing migrations is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
*
|
||||
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||
* with the Table class.
|
||||
*/
|
||||
public function change(): void
|
||||
{
|
||||
$exists = $this->table('audit_logs')->hasColumn('change');
|
||||
if ($exists) {
|
||||
$this->table('audit_logs')
|
||||
->renameColumn('change', 'changed')
|
||||
->update();
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -5,6 +5,16 @@
|
|||
* Note: It is not recommended to commit files with credentials such as app_local.php
|
||||
* into source code version control.
|
||||
*/
|
||||
|
||||
// set the baseurl here if you want to set it manually
|
||||
$baseurl = env('CEREBRATE_BASEURL', false);
|
||||
|
||||
|
||||
// Do not modify the this block
|
||||
$temp = parse_url($baseurl);
|
||||
$base = empty($temp['path']) ? false : $temp['path'];
|
||||
// end of block
|
||||
|
||||
return [
|
||||
/*
|
||||
* Debug Level:
|
||||
|
@ -89,4 +99,8 @@ return [
|
|||
'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
|
||||
],
|
||||
],
|
||||
'Cerebrate' => [
|
||||
'open' => [],
|
||||
'dark' => 0
|
||||
]
|
||||
];
|
||||
|
|
|
@ -7,4 +7,3 @@ webroot /usr/share/php-cerebrate
|
|||
config /usr/share/php-cerebrate
|
||||
debian/cerebrate.local.conf /etc/apache2/sites-available/
|
||||
debian/config.php /etc/cerebrate/
|
||||
INSTALL/mysql.sql => /usr/share/dbconfig-common/data/php-cerebrate/install/mysql
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
ARG COMPOSER_VERSION
|
||||
ARG PHP_VERSION
|
||||
ARG DEBIAN_RELEASE
|
||||
|
||||
FROM php:${PHP_VERSION}-apache-${DEBIAN_RELEASE}
|
||||
|
||||
# we need some extra libs to be installed in the runtime
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends curl git zip unzip && \
|
||||
apt-get install -y --no-install-recommends libicu-dev libxml2-dev && \
|
||||
docker-php-ext-install intl pdo pdo_mysql mysqli simplexml && \
|
||||
apt-get remove -y --purge libicu-dev libxml2-dev && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY composer.json composer.json
|
||||
|
||||
# install composer as root
|
||||
ARG COMPOSER_VERSION
|
||||
RUN curl -sL https://getcomposer.org/installer | \
|
||||
php -- --install-dir=/usr/bin/ --filename=composer --version=${COMPOSER_VERSION}
|
||||
|
||||
# switch back to unprivileged user for composer install
|
||||
USER www-data
|
||||
|
||||
RUN composer install \
|
||||
--no-interaction \
|
||||
--no-plugins \
|
||||
--no-scripts \
|
||||
--prefer-dist
|
||||
|
||||
# web server configuration
|
||||
USER root
|
||||
|
||||
# allow .htaccess overrides and push them
|
||||
RUN a2enmod rewrite
|
||||
RUN sed -i -r '/DocumentRoot/a \\t<Directory /var/www/html/>\n\t\tAllowOverride all\n\t</Directory>' /etc/apache2/sites-available/000-default.conf
|
||||
COPY --chown=www-data docker/etc/DocumentRoot.htaccess /var/www/html/.htaccess
|
||||
COPY --chown=www-data docker/etc/webroot.htaccess /var/www/html/webroot/.htaccess
|
||||
|
||||
# passing environment variables through apache
|
||||
RUN a2enmod env
|
||||
RUN echo 'PassEnv CEREBRATE_DB_HOST' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_DB_NAME' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_DB_PASSWORD' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_DB_PORT' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_DB_SCHEMA' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_DB_USERNAME' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_EMAIL_HOST' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_EMAIL_PASSWORD' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_EMAIL_PORT' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_EMAIL_TLS' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_EMAIL_USERNAME' >> /etc/apache2/conf-enabled/environment.conf
|
||||
RUN echo 'PassEnv CEREBRATE_SECURITY_SALT' >> /etc/apache2/conf-enabled/environment.conf
|
||||
|
||||
# entrypoint
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod 755 /entrypoint.sh
|
||||
|
||||
# copy actual codebase
|
||||
COPY --chown=www-data . /var/www/html
|
||||
|
||||
# last checks with unprivileged user
|
||||
USER www-data
|
||||
|
||||
# CakePHP seems to not handle very well externally installed components
|
||||
# this will chown/chmod/symlink all in place for its own good
|
||||
RUN composer install --no-interaction
|
||||
|
||||
# app config override making use of environment variables
|
||||
COPY --chown=www-data docker/etc/app_local.php /var/www/html/config/app_local.php
|
||||
# version 1.0 addition requires a config/config.json file
|
||||
# can still be overriden by a docker volume
|
||||
RUN cp -a /var/www/html/config/config.example.json /var/www/html/config/config.json
|
||||
|
||||
# also can be overridin by a docker volume
|
||||
RUN mkdir -p /var/www/html/logs
|
||||
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
|
@ -0,0 +1,30 @@
|
|||
# Actual data and volumes
|
||||
|
||||
The actual database will be located in `./run/database` exposed with the
|
||||
following volume `- ./run/database:/var/lib/mysql`
|
||||
|
||||
Application logs (CakePHP / Cerebrate) will be stored in `./run/logs`,
|
||||
volume `- ./run/logs:/var/www/html/logs`
|
||||
|
||||
You're free to change those parameters if you're using Swarm, Kubernetes or
|
||||
your favorite config management tool to deploy this stack
|
||||
|
||||
# Building yourself
|
||||
|
||||
You can create the following Makefile in basedir of this repository
|
||||
and issue `make image`
|
||||
|
||||
```
|
||||
COMPOSER_VERSION?=2.1.5
|
||||
PHP_VERSION?=7.4
|
||||
DEBIAN_RELEASE?=buster
|
||||
IMAGE_NAME?=cerebrate:latest
|
||||
|
||||
image:
|
||||
docker build -t $(IMAGE_NAME) \
|
||||
-f docker/Dockerfile \
|
||||
--build-arg COMPOSER_VERSION=$(COMPOSER_VERSION) \
|
||||
--build-arg PHP_VERSION=$(PHP_VERSION) \
|
||||
--build-arg DEBIAN_RELEASE=$(DEBIAN_RELEASE) \
|
||||
.
|
||||
```
|
|
@ -0,0 +1,29 @@
|
|||
version: "3"
|
||||
services:
|
||||
database:
|
||||
image: mariadb:10.6
|
||||
restart: always
|
||||
volumes:
|
||||
- ./run/database:/var/lib/mysql
|
||||
environment:
|
||||
MARIADB_RANDOM_ROOT_PASSWORD: "yes"
|
||||
MYSQL_DATABASE: "cerebrate"
|
||||
MYSQL_USER: "cerebrate"
|
||||
MYSQL_PASSWORD: "etarberec"
|
||||
www:
|
||||
image: ghcr.io/cerebrate-project/cerebrate:main
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./run/logs:/var/www/html/logs
|
||||
- ./wait-for-it.sh:/usr/local/bin/wait-for-it.sh:ro
|
||||
entrypoint: wait-for-it.sh -t 0 -h database -p 3306 -- /entrypoint.sh
|
||||
environment:
|
||||
DEBUG: "true"
|
||||
CEREBRATE_DB_USERNAME: "cerebrate"
|
||||
CEREBRATE_DB_PASSWORD: "etarberec"
|
||||
CEREBRATE_DB_NAME: "cerebrate"
|
||||
CEREBRATE_DB_HOST: database
|
||||
CEREBRATE_SECURITY_SALT: supersecret
|
||||
depends_on:
|
||||
- database
|
|
@ -0,0 +1,28 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
run_all_migrations() {
|
||||
./bin/cake migrations migrate
|
||||
./bin/cake migrations migrate -p tags
|
||||
./bin/cake migrations migrate -p ADmad/SocialAuth
|
||||
}
|
||||
|
||||
delete_model_cache() {
|
||||
echo >&2 "Deleting cakephp cache..."
|
||||
rm -rf ./tmp/cache/models/*
|
||||
rm -rf ./tmp/cache/persistent/*
|
||||
}
|
||||
|
||||
delete_model_cache
|
||||
|
||||
# waiting for DB to come up
|
||||
for try in 1 2 3 4 5 6; do
|
||||
echo >&2 "migration - attempt $try"
|
||||
run_all_migrations && break || true
|
||||
sleep 5
|
||||
[ "$try" = "6" ] && exit 1
|
||||
done
|
||||
|
||||
exec /usr/local/bin/apache2-foreground "$@"
|
|
@ -0,0 +1,3 @@
|
|||
RewriteEngine on
|
||||
RewriteRule ^$ webroot/ [L]
|
||||
RewriteRule (.*) webroot/$1 [L]
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
$db = [
|
||||
'username' => env('CEREBRATE_DB_USERNAME', 'cerebrate'),
|
||||
'password' => env('CEREBRATE_DB_PASSWORD', ''),
|
||||
'host' => env('CEREBRATE_DB_HOST', 'localhost'),
|
||||
'database' => env('CEREBRATE_DB_NAME', 'cerebrate'),
|
||||
];
|
||||
|
||||
// non-default port can be set on demand - otherwise the DB driver will choose the default
|
||||
if (!empty(env('CEREBRATE_DB_PORT'))) {
|
||||
$db['port'] = env('CEREBRATE_DB_PORT');
|
||||
}
|
||||
|
||||
// If not using the default 'public' schema with the PostgreSQL driver set it here.
|
||||
if (!empty(env('CEREBRATE_DB_SCHEMA'))) {
|
||||
$db['schema'] = env('CEREBRATE_DB_SCHEMA');
|
||||
}
|
||||
|
||||
return [
|
||||
'debug' => filter_var(env('DEBUG', false), FILTER_VALIDATE_BOOLEAN),
|
||||
|
||||
'Security' => [
|
||||
'salt' => env('CEREBRATE_SECURITY_SALT'),
|
||||
],
|
||||
|
||||
'Datasources' => [
|
||||
'default' => $db,
|
||||
],
|
||||
|
||||
'EmailTransport' => [
|
||||
'default' => [
|
||||
// host could be ssl://smtp.gmail.com then set port to 465
|
||||
'host' => env('CEREBRATE_EMAIL_HOST', 'localhost'),
|
||||
'port' => env('CEREBRATE_EMAIL_PORT', 25),
|
||||
'username' => env('CEREBRATE_EMAIL_USERNAME', null),
|
||||
'password' => env('CEREBRATE_EMAIL_PASSWORD', null),
|
||||
'tls' => env('CEREBRATE_EMAIL_TLS', null)
|
||||
],
|
||||
],
|
||||
'Cerebrate' => [
|
||||
'open' => [],
|
||||
'dark' => 0
|
||||
]
|
||||
];
|
|
@ -0,0 +1,3 @@
|
|||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/env bash
|
||||
# Use this script to test if a given TCP host/port are available
|
||||
|
||||
# The MIT License (MIT)
|
||||
# Copyright (c) 2016 Giles Hall
|
||||
# Source: https://github.com/vishnubob/wait-for-it
|
||||
|
||||
WAITFORIT_cmdname=${0##*/}
|
||||
|
||||
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
|
||||
|
||||
usage()
|
||||
{
|
||||
cat << USAGE >&2
|
||||
Usage:
|
||||
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
|
||||
-h HOST | --host=HOST Host or IP under test
|
||||
-p PORT | --port=PORT TCP port under test
|
||||
Alternatively, you specify the host and port as host:port
|
||||
-s | --strict Only execute subcommand if the test succeeds
|
||||
-q | --quiet Don't output any status messages
|
||||
-t TIMEOUT | --timeout=TIMEOUT
|
||||
Timeout in seconds, zero for no timeout
|
||||
-- COMMAND ARGS Execute command with args after the test finishes
|
||||
USAGE
|
||||
exit 1
|
||||
}
|
||||
|
||||
wait_for()
|
||||
{
|
||||
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
|
||||
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
|
||||
else
|
||||
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
|
||||
fi
|
||||
WAITFORIT_start_ts=$(date +%s)
|
||||
while :
|
||||
do
|
||||
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
|
||||
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
|
||||
WAITFORIT_result=$?
|
||||
else
|
||||
(echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
|
||||
WAITFORIT_result=$?
|
||||
fi
|
||||
if [[ $WAITFORIT_result -eq 0 ]]; then
|
||||
WAITFORIT_end_ts=$(date +%s)
|
||||
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return $WAITFORIT_result
|
||||
}
|
||||
|
||||
wait_for_wrapper()
|
||||
{
|
||||
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
|
||||
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
|
||||
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
|
||||
else
|
||||
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
|
||||
fi
|
||||
WAITFORIT_PID=$!
|
||||
trap "kill -INT -$WAITFORIT_PID" INT
|
||||
wait $WAITFORIT_PID
|
||||
WAITFORIT_RESULT=$?
|
||||
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
|
||||
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
|
||||
fi
|
||||
return $WAITFORIT_RESULT
|
||||
}
|
||||
|
||||
# process arguments
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
*:* )
|
||||
WAITFORIT_hostport=(${1//:/ })
|
||||
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
|
||||
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
|
||||
shift 1
|
||||
;;
|
||||
--child)
|
||||
WAITFORIT_CHILD=1
|
||||
shift 1
|
||||
;;
|
||||
-q | --quiet)
|
||||
WAITFORIT_QUIET=1
|
||||
shift 1
|
||||
;;
|
||||
-s | --strict)
|
||||
WAITFORIT_STRICT=1
|
||||
shift 1
|
||||
;;
|
||||
-h)
|
||||
WAITFORIT_HOST="$2"
|
||||
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
|
||||
shift 2
|
||||
;;
|
||||
--host=*)
|
||||
WAITFORIT_HOST="${1#*=}"
|
||||
shift 1
|
||||
;;
|
||||
-p)
|
||||
WAITFORIT_PORT="$2"
|
||||
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
|
||||
shift 2
|
||||
;;
|
||||
--port=*)
|
||||
WAITFORIT_PORT="${1#*=}"
|
||||
shift 1
|
||||
;;
|
||||
-t)
|
||||
WAITFORIT_TIMEOUT="$2"
|
||||
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
|
||||
shift 2
|
||||
;;
|
||||
--timeout=*)
|
||||
WAITFORIT_TIMEOUT="${1#*=}"
|
||||
shift 1
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
WAITFORIT_CLI=("$@")
|
||||
break
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echoerr "Unknown argument: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
|
||||
echoerr "Error: you need to provide a host and port to test."
|
||||
usage
|
||||
fi
|
||||
|
||||
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
|
||||
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
|
||||
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
|
||||
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
|
||||
|
||||
# Check to see if timeout is from busybox?
|
||||
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
|
||||
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
|
||||
|
||||
WAITFORIT_BUSYTIMEFLAG=""
|
||||
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
|
||||
WAITFORIT_ISBUSY=1
|
||||
# Check if busybox timeout uses -t flag
|
||||
# (recent Alpine versions don't support -t anymore)
|
||||
if timeout &>/dev/stdout | grep -q -e '-t '; then
|
||||
WAITFORIT_BUSYTIMEFLAG="-t"
|
||||
fi
|
||||
else
|
||||
WAITFORIT_ISBUSY=0
|
||||
fi
|
||||
|
||||
if [[ $WAITFORIT_CHILD -gt 0 ]]; then
|
||||
wait_for
|
||||
WAITFORIT_RESULT=$?
|
||||
exit $WAITFORIT_RESULT
|
||||
else
|
||||
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
|
||||
wait_for_wrapper
|
||||
WAITFORIT_RESULT=$?
|
||||
else
|
||||
wait_for
|
||||
WAITFORIT_RESULT=$?
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $WAITFORIT_CLI != "" ]]; then
|
||||
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
|
||||
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
|
||||
exit $WAITFORIT_RESULT
|
||||
fi
|
||||
exec "${WAITFORIT_CLI[@]}"
|
||||
else
|
||||
exit $WAITFORIT_RESULT
|
||||
fi
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,17 @@
|
|||
# Prerequisites based on usecases
|
||||
|
||||
This document list the requirements that have to be met in order to perform the desired usecase.
|
||||
|
||||
## Connect a local tool to cerebrate
|
||||
- **Networking**: The *cerebrate* application must be able to contact the local tool service. That means the address and the port of the local tool must be reachable by *cerebrate*.
|
||||
- **User permissions**: Depends on the actions performed by Cerebrate on the local tool.
|
||||
- Example: For a standard MISP configuration, a simple user with the `user` role is enough for Cerebrate to pass the health check.
|
||||
|
||||
## Conect two cerebrate instances together
|
||||
- **Networking**: The two *cerebrate* applications must be able to contact each others. That means the address and the port of both tools must be reachable by the other one.
|
||||
- **User permissions**: No specific role or set of permission is required. Any user role can be used.
|
||||
|
||||
## Connect two local tools through cerebrate
|
||||
- **Networking**: The two *cerebrate* applications must be able to contact each others. That means the address and the port of both tools must be reachable by the other one. This also applies to both the local tools.
|
||||
- **User permissions**: Depends on the actions performed by Cerebrate on the local tool.
|
||||
- Example: For a standard MISP configuration, in order to have two instance connected, the API key used by *cebrate* to orchestrate the inter-connection must belong to a user having the `site-admin` permission flag. This is essential as only the `site-admin` permission allows to create synchronisation links between MISP instances.
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
colors="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
>
|
||||
<phpunit colors="true" processIsolation="false" stopOnFailure="false" bootstrap="tests/bootstrap.php">
|
||||
<php>
|
||||
<ini name="memory_limit" value="-1"/>
|
||||
<ini name="apc.enable_cli" value="1"/>
|
||||
<ini name="memory_limit" value="-1" />
|
||||
<ini name="apc.enable_cli" value="1" />
|
||||
<env name="WIREMOCK_HOST" value="localhost" />
|
||||
<env name="WIREMOCK_PORT" value="8080" />
|
||||
<env name="OPENAPI_SPEC" value="webroot/docs/openapi.yaml" />
|
||||
<env name="SKIP_DB_MIGRATIONS" value="0" />
|
||||
</php>
|
||||
|
||||
<!-- Add any additional test suites you want to run here -->
|
||||
|
@ -15,17 +14,17 @@
|
|||
<testsuite name="app">
|
||||
<directory>tests/TestCase/</directory>
|
||||
</testsuite>
|
||||
<!-- Add plugin test suites here. -->
|
||||
<testsuite name="controller">
|
||||
<directory>./tests/TestCase/Controller</directory>
|
||||
</testsuite>
|
||||
<testsuite name="api">
|
||||
<directory>./tests/TestCase/Api</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<!-- Setup a listener for fixtures -->
|
||||
<listeners>
|
||||
<listener class="Cake\TestSuite\Fixture\FixtureInjector">
|
||||
<arguments>
|
||||
<object class="Cake\TestSuite\Fixture\FixtureManager"/>
|
||||
</arguments>
|
||||
</listener>
|
||||
</listeners>
|
||||
<extensions>
|
||||
<extension class="\Cake\TestSuite\Fixture\PHPUnitExtension" />
|
||||
</extensions>
|
||||
|
||||
<!-- Ignore vendor tests in code coverage reports -->
|
||||
<filter>
|
||||
|
|
|
@ -10,22 +10,22 @@ class TagSystem extends AbstractMigration
|
|||
$tags = $this->table('tags_tags');
|
||||
$tags->addColumn('namespace', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'limit' => 191,
|
||||
'null' => true,
|
||||
])
|
||||
->addColumn('predicate', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'limit' => 191,
|
||||
'null' => true,
|
||||
])
|
||||
->addColumn('value', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'limit' => 191,
|
||||
'null' => true,
|
||||
])
|
||||
->addColumn('name', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'limit' => 191,
|
||||
'null' => false,
|
||||
])
|
||||
->addColumn('colour', 'string', [
|
||||
|
@ -66,7 +66,7 @@ class TagSystem extends AbstractMigration
|
|||
])
|
||||
->addColumn('fk_model', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'limit' => 191,
|
||||
'null' => false,
|
||||
'comment' => 'The model name of the entity being tagged'
|
||||
])
|
||||
|
|
|
@ -114,7 +114,6 @@ class TagBehavior extends Behavior
|
|||
$property = $this->getConfig('tagsAssoc.propertyName');
|
||||
$options['accessibleFields'][$property] = true;
|
||||
$options['associated']['Tags']['accessibleFields']['id'] = true;
|
||||
|
||||
if (isset($data['tags'])) {
|
||||
if (!empty($data['tags'])) {
|
||||
$data[$property] = $this->normalizeTags($data['tags']);
|
||||
|
@ -131,7 +130,6 @@ class TagBehavior extends Behavior
|
|||
if (!$tag->isNew()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existingTag = $this->getExistingTag($tag->name);
|
||||
if (!$existingTag) {
|
||||
continue;
|
||||
|
@ -176,15 +174,14 @@ class TagBehavior extends Behavior
|
|||
$result[] = array_merge($common, ['id' => $existingTag->id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = array_merge(
|
||||
$common,
|
||||
[
|
||||
'name' => $tagIdentifier,
|
||||
'colour' => '#924da6'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
|
|
@ -118,6 +118,7 @@ class Application extends BaseApplication implements AuthenticationServiceProvid
|
|||
'collectionFactory' => null,
|
||||
'logErrors' => true,
|
||||
]));
|
||||
\SocialConnect\JWX\JWT::$screw = Configure::check('keycloak.screw') ? Configure::read('keycloak.screw') : 0;
|
||||
}
|
||||
$middlewareQueue->add(new AuthenticationMiddleware($this))
|
||||
->add(new BodyParserMiddleware());
|
||||
|
|
|
@ -165,7 +165,7 @@ class ImporterCommand extends Command
|
|||
'valueField' => 'id'
|
||||
])->where(['meta_template_id' => $metaTemplate->id])->toArray();
|
||||
} else {
|
||||
$this->io->error("Unkown template for UUID {$config['metaTemplateUUID']}");
|
||||
$this->io->error("Unknown template for UUID {$config['metaTemplateUUID']}");
|
||||
die(1);
|
||||
}
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ class ImporterCommand extends Command
|
|||
$metaEntity->meta_template_field_id = $metaTemplateFieldsMapping[$fieldName];
|
||||
} else {
|
||||
$hasErrors = true;
|
||||
$this->io->error("Field $fieldName is unkown for template {$metaTemplate->name}");
|
||||
$this->io->error("Field $fieldName is unknown for template {$metaTemplate->name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
|
||||
class ApiController extends AppController
|
||||
{
|
||||
/**
|
||||
* Controller action for displaying built-in Redoc UI
|
||||
*
|
||||
* @return \Cake\Http\Response|null|void Renders view
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$url = '/docs/openapi.yaml';
|
||||
$this->set('url', $url);
|
||||
}
|
||||
}
|
|
@ -54,7 +54,6 @@ class AppController extends Controller
|
|||
public function initialize(): void
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
$this->loadComponent('RequestHandler');
|
||||
$this->loadComponent('Flash');
|
||||
$this->loadComponent('RestResponse');
|
||||
|
@ -102,7 +101,7 @@ class AppController extends Controller
|
|||
$this->ACL->setPublicInterfaces();
|
||||
if (!empty($this->request->getAttribute('identity'))) {
|
||||
$user = $this->Users->get($this->request->getAttribute('identity')->getIdentifier(), [
|
||||
'contain' => ['Roles', 'Individuals' => 'Organisations', 'UserSettings']
|
||||
'contain' => ['Roles', 'Individuals' => 'Organisations', 'UserSettings', 'Organisations']
|
||||
]);
|
||||
if (!empty($user['disabled'])) {
|
||||
$this->Authentication->logout();
|
||||
|
@ -111,9 +110,13 @@ class AppController extends Controller
|
|||
}
|
||||
unset($user['password']);
|
||||
$this->ACL->setUser($user);
|
||||
$this->request->getSession()->write('authUser', $user);
|
||||
$this->isAdmin = $user['role']['perm_admin'];
|
||||
$this->set('menu', $this->ACL->getMenu());
|
||||
$this->set('loggedUser', $this->ACL->getUser());
|
||||
if (!$this->ParamHandler->isRest()) {
|
||||
$this->set('menu', $this->ACL->getMenu());
|
||||
$this->set('loggedUser', $this->ACL->getUser());
|
||||
$this->set('roleAccess', $this->ACL->getRoleAccess(false, false));
|
||||
}
|
||||
} else if ($this->ParamHandler->isRest()) {
|
||||
throw new MethodNotAllowedException(__('Invalid user credentials.'));
|
||||
}
|
||||
|
@ -128,17 +131,20 @@ class AppController extends Controller
|
|||
}
|
||||
|
||||
$this->ACL->checkAccess();
|
||||
$this->set('ajax', $this->request->is('ajax'));
|
||||
$this->request->getParam('prefix');
|
||||
$this->set('baseurl', Configure::read('App.fullBaseUrl'));
|
||||
if (!empty($user) && !empty($user->user_settings_by_name['ui.bsTheme']['value'])) {
|
||||
$this->set('bsTheme', $user->user_settings_by_name['ui.bsTheme']['value']);
|
||||
} else {
|
||||
$this->set('bsTheme', Configure::check('ui.bsTheme') ? Configure::read('ui.bsTheme') : 'default');
|
||||
}
|
||||
if (!$this->ParamHandler->isRest()) {
|
||||
$this->set('breadcrumb', $this->Navigation->getBreadcrumb());
|
||||
$this->set('ajax', $this->request->is('ajax'));
|
||||
$this->request->getParam('prefix');
|
||||
$this->set('baseurl', Configure::read('App.fullBaseUrl'));
|
||||
if (!empty($user) && !empty($user->user_settings_by_name['ui.bsTheme']['value'])) {
|
||||
$this->set('bsTheme', $user->user_settings_by_name['ui.bsTheme']['value']);
|
||||
} else {
|
||||
$this->set('bsTheme', Configure::check('ui.bsTheme') ? Configure::read('ui.bsTheme') : 'default');
|
||||
}
|
||||
|
||||
if ($this->modelClass == 'Tags.Tags') {
|
||||
$this->set('metaGroup', !empty($this->isAdmin) ? 'Administration' : 'Cerebrate');
|
||||
if ($this->modelClass == 'Tags.Tags') {
|
||||
$this->set('metaGroup', !empty($this->isAdmin) ? 'Administration' : 'Cerebrate');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,13 +157,30 @@ class AppController extends Controller
|
|||
{
|
||||
if (!empty($_SERVER['HTTP_AUTHORIZATION']) && strlen($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||
$this->loadModel('AuthKeys');
|
||||
$logModel = $this->Users->auditLogs();
|
||||
$authKey = $this->AuthKeys->checkKey($_SERVER['HTTP_AUTHORIZATION']);
|
||||
if (!empty($authKey)) {
|
||||
$this->loadModel('Users');
|
||||
$user = $this->Users->get($authKey['user_id']);
|
||||
$logModel->insert([
|
||||
'request_action' => 'login',
|
||||
'model' => 'Users',
|
||||
'model_id' => $user['id'],
|
||||
'model_title' => $user['username'],
|
||||
'changed' => []
|
||||
]);
|
||||
if (!empty($user)) {
|
||||
$this->Authentication->setIdentity($user);
|
||||
}
|
||||
} else {
|
||||
$user = $logModel->userInfo();
|
||||
$logModel->insert([
|
||||
'request_action' => 'login',
|
||||
'model' => 'Users',
|
||||
'model_id' => $user['id'],
|
||||
'model_title' => $user['name'],
|
||||
'changed' => []
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -172,4 +195,9 @@ class AppController extends Controller
|
|||
{
|
||||
return $this->RestResponse->viewData($this->ACL->findMissingFunctionNames());
|
||||
}
|
||||
|
||||
public function getRoleAccess()
|
||||
{
|
||||
return $this->RestResponse->viewData($this->ACL->getRoleAccess(false, false));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use \Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Http\Exception\UnauthorizedException;
|
||||
use Cake\Core\Configure;
|
||||
|
||||
class AuditLogsController extends AppController
|
||||
{
|
||||
public $filterFields = ['model_id', 'model', 'request_action', 'user_id', 'title'];
|
||||
public $quickFilterFields = ['model', 'request_action', 'title'];
|
||||
public $containFields = ['Users'];
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->CRUD->index([
|
||||
'contain' => $this->containFields,
|
||||
'filters' => $this->filterFields,
|
||||
'quickFilters' => $this->quickFilterFields,
|
||||
'afterFind' => function($data) {
|
||||
$data['request_ip'] = inet_ntop(stream_get_contents($data['request_ip']));
|
||||
$data['changed'] = stream_get_contents($data['changed']);
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$this->set('metaGroup', 'Administration');
|
||||
}
|
||||
}
|
|
@ -16,15 +16,25 @@ class AuthKeysController extends AppController
|
|||
{
|
||||
public $filterFields = ['Users.username', 'authkey', 'comment', 'Users.id'];
|
||||
public $quickFilterFields = ['authkey', ['comment' => true]];
|
||||
public $containFields = ['Users'];
|
||||
public $containFields = ['Users' => ['fields' => ['id', 'username']]];
|
||||
|
||||
public function index()
|
||||
{
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$conditions = [];
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$conditions['Users.organisation_id'] = $currentUser['organisation_id'];
|
||||
if (empty($currentUser['role']['perm_org_admin'])) {
|
||||
$conditions['Users.id'] = $currentUser['id'];
|
||||
}
|
||||
}
|
||||
$this->CRUD->index([
|
||||
'filters' => $this->filterFields,
|
||||
'quickFilters' => $this->quickFilterFields,
|
||||
'contain' => $this->containFields,
|
||||
'exclude_fields' => ['authkey']
|
||||
'exclude_fields' => ['authkey'],
|
||||
'conditions' => $conditions,
|
||||
'hidden' => []
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
|
@ -35,7 +45,15 @@ class AuthKeysController extends AppController
|
|||
|
||||
public function delete($id)
|
||||
{
|
||||
$this->CRUD->delete($id);
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$conditions = [];
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$conditions['Users.organisation_id'] = $currentUser['organisation_id'];
|
||||
if (empty($currentUser['role']['perm_org_admin'])) {
|
||||
$conditions['Users.id'] = $currentUser['id'];
|
||||
}
|
||||
}
|
||||
$this->CRUD->delete($id, ['conditions' => $conditions, 'contain' => 'Users']);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
|
@ -46,8 +64,30 @@ class AuthKeysController extends AppController
|
|||
public function add()
|
||||
{
|
||||
$this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate');
|
||||
$validUsers = [];
|
||||
$userConditions = [];
|
||||
$currentUser = $this->ACL->getUser();
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
if (empty($currentUser['role']['perm_org_admin'])) {
|
||||
$userConditions['id'] = $currentUser['id'];
|
||||
} else {
|
||||
$role_ids = $this->Users->Roles->find()->where(['perm_admin' => 0])->all()->extract('id')->toList();
|
||||
$userConditions['role_id IN'] = $role_ids;
|
||||
}
|
||||
}
|
||||
$users = $this->Users->find('list');
|
||||
if (!empty($userConditions)) {
|
||||
$users->where($userConditions);
|
||||
}
|
||||
$users = $users->order(['username' => 'asc'])->all()->toArray();
|
||||
$this->CRUD->add([
|
||||
'displayOnSuccess' => 'authkey_display'
|
||||
'displayOnSuccess' => 'authkey_display',
|
||||
'beforeSave' => function($data) use ($users) {
|
||||
if (!in_array($data['user_id'], array_keys($users))) {
|
||||
return false;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload([
|
||||
'displayOnSuccess' => 'authkey_display'
|
||||
|
@ -57,9 +97,7 @@ class AuthKeysController extends AppController
|
|||
}
|
||||
$this->loadModel('Users');
|
||||
$dropdownData = [
|
||||
'user' => $this->Users->find('list', [
|
||||
'sort' => ['username' => 'asc']
|
||||
])
|
||||
'user' => $users
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ class ACLComponent extends Component
|
|||
'*' => [
|
||||
'checkPermission' => ['*'],
|
||||
'generateUUID' => ['*'],
|
||||
'getRoleAccess' => ['*'],
|
||||
'queryACL' => ['perm_admin']
|
||||
],
|
||||
'Alignments' => [
|
||||
|
@ -45,6 +46,9 @@ class ACLComponent extends Component
|
|||
'index' => ['*'],
|
||||
'view' => ['*']
|
||||
],
|
||||
'AuditLogs' => [
|
||||
'index' => ['perm_admin']
|
||||
],
|
||||
'AuthKeys' => [
|
||||
'add' => ['*'],
|
||||
'delete' => ['*'],
|
||||
|
@ -64,6 +68,7 @@ class ACLComponent extends Component
|
|||
'view' => ['perm_admin']
|
||||
],
|
||||
'EncryptionKeys' => [
|
||||
'view' => ['*'],
|
||||
'add' => ['*'],
|
||||
'edit' => ['*'],
|
||||
'delete' => ['*'],
|
||||
|
@ -82,22 +87,30 @@ class ACLComponent extends Component
|
|||
'add' => ['perm_admin'],
|
||||
'delete' => ['perm_admin'],
|
||||
'edit' => ['perm_admin'],
|
||||
'filtering' => ['*'],
|
||||
'index' => ['*'],
|
||||
'view' => ['*']
|
||||
'tag' => ['perm_tagger'],
|
||||
'untag' => ['perm_tagger'],
|
||||
'view' => ['*'],
|
||||
'viewTags' => ['*']
|
||||
],
|
||||
'Instance' => [
|
||||
'home' => ['*'],
|
||||
'migrate' => ['perm_admin'],
|
||||
'migrationIndex' => ['perm_admin'],
|
||||
'rollback' => ['perm_admin'],
|
||||
'saveSetting' => ['perm_admin'],
|
||||
'searchAll' => ['*'],
|
||||
'settings' => ['perm_admin'],
|
||||
'status' => ['*']
|
||||
],
|
||||
'LocalTools' => [
|
||||
'action' => ['perm_admin'],
|
||||
'add' => ['perm_admin'],
|
||||
'batchAction' => ['perm_admin'],
|
||||
'broodTools' => ['perm_admin'],
|
||||
'connectionRequest' => ['perm_admin'],
|
||||
'connectLocal' => ['perm_admin'],
|
||||
// 'connectLocal' => ['perm_admin'],
|
||||
'delete' => ['perm_admin'],
|
||||
'edit' => ['perm_admin'],
|
||||
'exposedTools' => ['OR' => ['perm_admin', 'perm_sync']],
|
||||
|
@ -123,7 +136,10 @@ class ACLComponent extends Component
|
|||
'edit' => ['perm_admin'],
|
||||
'filtering' => ['*'],
|
||||
'index' => ['*'],
|
||||
'view' => ['*']
|
||||
'tag' => ['perm_tagger'],
|
||||
'untag' => ['perm_tagger'],
|
||||
'view' => ['*'],
|
||||
'viewTags' => ['*']
|
||||
],
|
||||
'Outbox' => [
|
||||
'createEntry' => ['perm_admin'],
|
||||
|
@ -145,25 +161,42 @@ class ACLComponent extends Component
|
|||
'view' => ['*']
|
||||
],
|
||||
'SharingGroups' => [
|
||||
'add' => ['perm_admin'],
|
||||
'addOrg' => ['perm_admin'],
|
||||
'delete' => ['perm_admin'],
|
||||
'edit' => ['perm_admin'],
|
||||
'add' => ['perm_org_admin'],
|
||||
'addOrg' => ['perm_org_admin'],
|
||||
'delete' => ['perm_org_admin'],
|
||||
'edit' => ['perm_org_admin'],
|
||||
'index' => ['*'],
|
||||
'listOrgs' => ['*'],
|
||||
'removeOrg' => ['perm_admin'],
|
||||
'removeOrg' => ['perm_org_admin'],
|
||||
'view' => ['*']
|
||||
],
|
||||
'Users' => [
|
||||
'add' => ['perm_admin'],
|
||||
'delete' => ['perm_admin'],
|
||||
'add' => ['perm_org_admin'],
|
||||
'delete' => ['perm_org_admin'],
|
||||
'edit' => ['*'],
|
||||
'index' => ['perm_admin'],
|
||||
'index' => ['perm_org_admin'],
|
||||
'login' => ['*'],
|
||||
'logout' => ['*'],
|
||||
'register' => ['*'],
|
||||
'toggle' => ['perm_admin'],
|
||||
'settings' => ['*'],
|
||||
'toggle' => ['perm_org_admin'],
|
||||
'view' => ['*']
|
||||
],
|
||||
'UserSettings' => [
|
||||
'index' => ['*'],
|
||||
'view' => ['*'],
|
||||
'add' => ['*'],
|
||||
'edit' => ['*'],
|
||||
'delete' => ['*'],
|
||||
'getSettingByName' => ['*'],
|
||||
'setSetting' => ['*'],
|
||||
'saveSetting' => ['*'],
|
||||
'getBookmarks' => ['*'],
|
||||
'saveBookmark' => ['*'],
|
||||
'deleteBookmark' => ['*']
|
||||
],
|
||||
'Api' => [
|
||||
'index' => ['*']
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -267,9 +300,19 @@ class ACLComponent extends Component
|
|||
return true;
|
||||
}
|
||||
//$this->__checkLoggedActions($user, $controller, $action);
|
||||
if (isset($this->aclList['*'][$action])) {
|
||||
if ($this->evaluateAccessLeaf('*', $action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!isset($this->aclList[$controller])) {
|
||||
return $this->__error(404, __('Invalid controller.'), $soft);
|
||||
}
|
||||
return $this->evaluateAccessLeaf($controller, $action);
|
||||
}
|
||||
|
||||
private function evaluateAccessLeaf(string $controller, string $action): bool
|
||||
{
|
||||
if (isset($this->aclList[$controller][$action]) && !empty($this->aclList[$controller][$action])) {
|
||||
if (in_array('*', $this->aclList[$controller][$action])) {
|
||||
return true;
|
||||
|
@ -290,6 +333,12 @@ class ACLComponent extends Component
|
|||
if ($allConditionsMet) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
foreach ($this->aclList[$controller][$action] as $permission) {
|
||||
if ($this->user['role'][$permission]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -391,13 +440,18 @@ class ACLComponent extends Component
|
|||
return $missing;
|
||||
}
|
||||
|
||||
public function getRoleAccess($role = false, $url_mode = true)
|
||||
{
|
||||
return $this->__checkRoleAccess($role, $url_mode);
|
||||
}
|
||||
|
||||
public function printRoleAccess($content = false)
|
||||
{
|
||||
$results = [];
|
||||
$this->Role = TableRegistry::get('Role');
|
||||
$this->Role = TableRegistry::get('Roles');
|
||||
$conditions = [];
|
||||
if (is_numeric($content)) {
|
||||
$conditions = array('Role.id' => $content);
|
||||
$conditions = array('id' => $content);
|
||||
}
|
||||
$roles = $this->Role->find('all', array(
|
||||
'recursive' => -1,
|
||||
|
@ -413,44 +467,54 @@ class ACLComponent extends Component
|
|||
return $results;
|
||||
}
|
||||
|
||||
private function __checkRoleAccess($role)
|
||||
private function __formatControllerAction(array $results, string $controller, string $action, $url_mode = true): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this->__aclList as $controller => $actions) {
|
||||
$controllerNames = Inflector::variable($controller) == Inflector::underscore($controller) ? array(Inflector::variable($controller)) : array(Inflector::variable($controller), Inflector::underscore($controller));
|
||||
foreach ($controllerNames as $controllerName) {
|
||||
foreach ($actions as $action => $permissions) {
|
||||
if ($role['perm_site_admin']) {
|
||||
$result[] = DS . $controllerName . DS . $action;
|
||||
} elseif (in_array('*', $permissions)) {
|
||||
$result[] = DS . $controllerName . DS . $action . DS . '*';
|
||||
} elseif (isset($permissions['OR'])) {
|
||||
$access = false;
|
||||
foreach ($permissions['OR'] as $permission) {
|
||||
if ($role[$permission]) {
|
||||
$access = true;
|
||||
}
|
||||
if ($url_mode) {
|
||||
$results[] = DS . $controller . DS . $action . DS . '*';
|
||||
} else {
|
||||
$results[$controller][] = $action;
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function __checkRoleAccess($role = false, $url_mode = true)
|
||||
{
|
||||
$results = [];
|
||||
if ($role === false) {
|
||||
$role = $this->getUser()['role'];
|
||||
}
|
||||
foreach ($this->aclList as $controller => $actions) {
|
||||
foreach ($actions as $action => $permissions) {
|
||||
if ($role['perm_admin']) {
|
||||
$results = $this->__formatControllerAction($results, $controller, $action, $url_mode);
|
||||
} elseif (in_array('*', $permissions)) {
|
||||
$results = $this->__formatControllerAction($results, $controller, $action, $url_mode);
|
||||
} elseif (isset($permissions['OR'])) {
|
||||
$access = false;
|
||||
foreach ($permissions['OR'] as $permission) {
|
||||
if ($role[$permission]) {
|
||||
$access = true;
|
||||
}
|
||||
if ($access) {
|
||||
$result[] = DS . $controllerName . DS . $action . DS . '*';
|
||||
}
|
||||
} elseif (isset($permissions['AND'])) {
|
||||
$access = true;
|
||||
foreach ($permissions['AND'] as $permission) {
|
||||
if ($role[$permission]) {
|
||||
$access = false;
|
||||
}
|
||||
}
|
||||
if ($access) {
|
||||
$result[] = DS . $controllerName . DS . $action . DS . '*';
|
||||
}
|
||||
} elseif (isset($permissions[0]) && $role[$permissions[0]]) {
|
||||
$result[] = DS . $controllerName . DS . $action . DS . '*';
|
||||
}
|
||||
if ($access) {
|
||||
$results = $this->__formatControllerAction($results, $controller, $action, $url_mode);
|
||||
}
|
||||
} elseif (isset($permissions['AND'])) {
|
||||
$access = true;
|
||||
foreach ($permissions['AND'] as $permission) {
|
||||
if ($role[$permission]) {
|
||||
$access = false;
|
||||
}
|
||||
}
|
||||
if ($access) {
|
||||
$results = $this->__formatControllerAction($results, $controller, $action, $url_mode);
|
||||
}
|
||||
} elseif (isset($permissions[0]) && $role[$permissions[0]]) {
|
||||
$results = $this->__formatControllerAction($results, $controller, $action, $url_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function getMenu()
|
||||
|
|
|
@ -56,6 +56,9 @@ class CRUDComponent extends Component
|
|||
}
|
||||
$query = $this->setFilters($params, $query, $options);
|
||||
$query = $this->setQuickFilters($params, $query, $options);
|
||||
if (!empty($options['conditions'])) {
|
||||
$query->where($options['conditions']);
|
||||
}
|
||||
if (!empty($options['contain'])) {
|
||||
$query->contain($options['contain']);
|
||||
}
|
||||
|
@ -67,11 +70,25 @@ class CRUDComponent extends Component
|
|||
}
|
||||
if ($this->Controller->ParamHandler->isRest()) {
|
||||
$data = $query->all();
|
||||
if (isset($options['hidden'])) {
|
||||
$data->each(function($value, $key) use ($options) {
|
||||
$hidden = is_array($options['hidden']) ? $options['hidden'] : [$options['hidden']];
|
||||
$value->setHidden($hidden);
|
||||
return $value;
|
||||
});
|
||||
}
|
||||
if (isset($options['afterFind'])) {
|
||||
$function = $options['afterFind'];
|
||||
if (is_callable($options['afterFind'])) {
|
||||
$data = $options['afterFind']($data);
|
||||
$function = $options['afterFind'];
|
||||
$data->each(function($value, $key) use ($function) {
|
||||
return $function($value);
|
||||
});
|
||||
} else {
|
||||
$data = $this->Table->{$options['afterFind']}($data);
|
||||
$t = $this->Table;
|
||||
$data->each(function($value, $key) use ($t, $function) {
|
||||
return $t->$function($value);
|
||||
});
|
||||
}
|
||||
}
|
||||
$this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
|
||||
|
@ -82,10 +99,17 @@ class CRUDComponent extends Component
|
|||
$this->Controller->loadComponent('Paginator');
|
||||
$data = $this->Controller->Paginator->paginate($query);
|
||||
if (isset($options['afterFind'])) {
|
||||
$function = $options['afterFind'];
|
||||
if (is_callable($options['afterFind'])) {
|
||||
$data = $options['afterFind']($data);
|
||||
$function = $options['afterFind'];
|
||||
$data->each(function($value, $key) use ($function) {
|
||||
return $function($value);
|
||||
});
|
||||
} else {
|
||||
$data = $this->Table->{$options['afterFind']}($data);
|
||||
$t = $this->Table;
|
||||
$data->each(function($value, $key) use ($t, $function) {
|
||||
return $t->$function($value);
|
||||
});
|
||||
}
|
||||
}
|
||||
$this->setFilteringContext($options['contextFilters'] ?? [], $params);
|
||||
|
@ -220,6 +244,9 @@ class CRUDComponent extends Component
|
|||
$data = $this->Table->patchEntity($data, $input, $patchEntityParams);
|
||||
if (isset($params['beforeSave'])) {
|
||||
$data = $params['beforeSave']($data);
|
||||
if ($data === false) {
|
||||
throw new NotFoundException(__('Could not save {0} due to the input failing to meet expectations. Your input is bad and you should feel bad.', $this->ObjectAlias));
|
||||
}
|
||||
}
|
||||
$savedData = $this->Table->save($data);
|
||||
if ($savedData !== false) {
|
||||
|
@ -417,18 +444,25 @@ class CRUDComponent extends Component
|
|||
$params['contain'][] = 'Tags';
|
||||
$this->setAllTags();
|
||||
}
|
||||
$getParam = isset($params['get']) ? $params['get'] : $params;
|
||||
$queryParam = isset($params['get']) ? $params['get'] : $params;
|
||||
if ($this->metaFieldsSupported()) {
|
||||
if (empty($getParam['contain'])) {
|
||||
$getParam['contain'] = [];
|
||||
if (empty( $queryParam['contain'])) {
|
||||
$queryParam['contain'] = [];
|
||||
}
|
||||
if (is_array($getParam['contain'])) {
|
||||
$getParam['contain'][] = 'MetaFields';
|
||||
if (is_array( $queryParam['contain'])) {
|
||||
$queryParam['contain'][] = 'MetaFields';
|
||||
} else {
|
||||
$getParam['contain'] = [$getParam['contain'], 'MetaFields'];
|
||||
$queryParam['contain'] = [ $queryParam['contain'], 'MetaFields'];
|
||||
}
|
||||
}
|
||||
$data = $this->Table->get($id, $getParam);
|
||||
$query = $this->Table->find()->where(['id' => $id]);
|
||||
if (!empty($params['conditions'])) {
|
||||
$query->where($params['conditions']);
|
||||
}
|
||||
$data = $query->first();
|
||||
if (empty($data)) {
|
||||
throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
|
||||
}
|
||||
if ($this->metaFieldsSupported()) {
|
||||
$metaTemplates = $this->getMetaTemplates();
|
||||
$data = $this->attachMetaTemplatesIfNeeded($data, $metaTemplates->toArray());
|
||||
|
@ -653,11 +687,27 @@ class CRUDComponent extends Component
|
|||
return $data;
|
||||
}
|
||||
|
||||
public function delete($id = false): void
|
||||
public function delete($id=false, $params=[]): void
|
||||
{
|
||||
if ($this->request->is('get')) {
|
||||
if (!empty($id)) {
|
||||
$data = $this->Table->get($id);
|
||||
if(!empty($id)) {
|
||||
$query = $this->Table->find()->where([$this->Table->getAlias() . '.id' => $id]);
|
||||
if (!empty($params['conditions'])) {
|
||||
$query->where($params['conditions']);
|
||||
}
|
||||
if (!empty($params['contain'])) {
|
||||
$query->contain($params['contain']);
|
||||
}
|
||||
$data = $query->first();
|
||||
if (empty($data)) {
|
||||
throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
|
||||
}
|
||||
if (isset($params['beforeSave'])) {
|
||||
$data = $params['beforeSave']($data);
|
||||
if ($data === false) {
|
||||
throw new NotFoundException(__('Could not save {0} due to the input failing to meet expectations. Your input is bad and you should feel bad.', $this->ObjectAlias));
|
||||
}
|
||||
}
|
||||
$this->Controller->set('id', $data['id']);
|
||||
$this->Controller->set('data', $data);
|
||||
$this->Controller->set('bulkEnabled', false);
|
||||
|
@ -669,8 +719,26 @@ class CRUDComponent extends Component
|
|||
$isBulk = count($ids) > 1;
|
||||
$bulkSuccesses = 0;
|
||||
foreach ($ids as $id) {
|
||||
$data = $this->Table->get($id);
|
||||
$success = $this->Table->delete($data);
|
||||
$query = $this->Table->find()->where([$this->Table->getAlias() . '.id' => $id]);
|
||||
if (!empty($params['conditions'])) {
|
||||
$query->where($params['conditions']);
|
||||
}
|
||||
if (!empty($params['contain'])) {
|
||||
$query->contain($params['contain']);
|
||||
}
|
||||
$data = $query->first();
|
||||
if (isset($params['beforeSave'])) {
|
||||
$data = $params['beforeSave']($data);
|
||||
if ($data === false) {
|
||||
throw new NotFoundException(__('Could not save {0} due to the input failing to meet expectations. Your input is bad and you should feel bad.', $this->ObjectAlias));
|
||||
}
|
||||
}
|
||||
if (!empty($data)) {
|
||||
$success = $this->Table->delete($data);
|
||||
$success = true;
|
||||
} else {
|
||||
$success = false;
|
||||
}
|
||||
if ($success) {
|
||||
$bulkSuccesses++;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,12 @@ class Sidemenu {
|
|||
'label' => __('Broods'),
|
||||
'icon' => $this->iconTable['Broods'],
|
||||
'url' => '/broods/index',
|
||||
]
|
||||
],
|
||||
'API' => [
|
||||
'label' => __('API'),
|
||||
'icon' => $this->iconTable['API'],
|
||||
'url' => '/api/index',
|
||||
],
|
||||
],
|
||||
__('Administration') => [
|
||||
'Roles' => [
|
||||
|
@ -118,6 +123,11 @@ class Sidemenu {
|
|||
'url' => '/instance/migrationIndex',
|
||||
'icon' => 'database',
|
||||
],
|
||||
'AuditLogs' => [
|
||||
'label' => __('Audit Logs'),
|
||||
'url' => '/auditLogs/index',
|
||||
'icon' => 'history',
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
|
|
@ -35,6 +35,7 @@ class NavigationComponent extends Component
|
|||
'LocalTools' => 'tools',
|
||||
'Instance' => 'server',
|
||||
'Tags' => 'tags',
|
||||
'API' => 'code',
|
||||
];
|
||||
|
||||
public function initialize(array $config): void
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Controller\Component;
|
|||
|
||||
use Cake\Controller\Component;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
|
||||
class ParamHandlerComponent extends Component
|
||||
{
|
||||
|
@ -47,7 +48,7 @@ class ParamHandlerComponent extends Component
|
|||
return $this->isRest;
|
||||
}
|
||||
if ($this->request->is('json')) {
|
||||
if (!empty($this->request->input()) && empty($this->request->input('json_decode'))) {
|
||||
if (!empty((string)$this->request->getBody()) && empty($this->request->getParsedBody())) {
|
||||
throw new MethodNotAllowedException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.');
|
||||
}
|
||||
$this->isRest = true;
|
||||
|
|
|
@ -718,11 +718,11 @@ class RestResponseComponent extends Component
|
|||
'operators' => array('equal'),
|
||||
'help' => __('A valid x509 certificate ')
|
||||
),
|
||||
'change' => array(
|
||||
'changed' => array(
|
||||
'input' => 'text',
|
||||
'type' => 'string',
|
||||
'operators' => array('equal'),
|
||||
'help' => __('The text contained in the change field')
|
||||
'help' => __('The text contained in the changed field')
|
||||
),
|
||||
'change_pw' => array(
|
||||
'input' => 'radio',
|
||||
|
|
|
@ -14,7 +14,7 @@ use Cake\Error\Debugger;
|
|||
|
||||
class EncryptionKeysController extends AppController
|
||||
{
|
||||
public $filterFields = ['owner_model', 'organisation_id', 'individual_id', 'encryption_key'];
|
||||
public $filterFields = ['owner_model', 'owner_id', 'encryption_key'];
|
||||
public $quickFilterFields = ['encryption_key'];
|
||||
public $containFields = ['Individuals', 'Organisations'];
|
||||
public $statisticsFields = ['type'];
|
||||
|
@ -41,7 +41,15 @@ class EncryptionKeysController extends AppController
|
|||
|
||||
public function delete($id)
|
||||
{
|
||||
$this->CRUD->delete($id);
|
||||
$orgConditions = [];
|
||||
$individualConditions = [];
|
||||
$dropdownData = [];
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$params = [];
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$params = $this->buildBeforeSave($params, $currentUser, $orgConditions, $individualConditions, $dropdownData);
|
||||
}
|
||||
$this->CRUD->delete($id, $params);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
|
@ -49,35 +57,92 @@ class EncryptionKeysController extends AppController
|
|||
$this->set('metaGroup', 'ContactDB');
|
||||
}
|
||||
|
||||
public function add()
|
||||
private function buildBeforeSave(array $params, $currentUser, array &$orgConditions, array &$individualConditions, array &$dropdownData): array
|
||||
{
|
||||
$this->CRUD->add(['redirect' => $this->referer()]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$orgConditions = [
|
||||
'id' => $currentUser['organisation_id']
|
||||
];
|
||||
if (empty($currentUser['role']['perm_org_admin'])) {
|
||||
$individualConditions = [
|
||||
'id' => $currentUser['individual_id']
|
||||
];
|
||||
} else {
|
||||
$this->loadModel('Alignments');
|
||||
$individualConditions = ['id IN' => $this->Alignments->find('list', [
|
||||
'keyField' => 'id',
|
||||
'valueField' => 'individual_id',
|
||||
'conditions' => ['organisation_id' => $currentUser['organisation_id']]
|
||||
])->toArray()];
|
||||
}
|
||||
$params['beforeSave'] = function($entity) use($currentUser) {
|
||||
if ($entity['owner_model'] === 'organisation') {
|
||||
if ($entity['owner_id'] !== $currentUser['organisation_id']) {
|
||||
throw new MethodNotAllowedException(__('Selected organisation cannot be linked by the current user.'));
|
||||
}
|
||||
} else {
|
||||
if ($currentUser['role']['perm_org_admin']) {
|
||||
$this->loadModel('Alignments');
|
||||
$validIndividuals = $this->Alignments->find('list', [
|
||||
'keyField' => 'individual_id',
|
||||
'valueField' => 'id',
|
||||
'conditions' => ['organisation_id' => $currentUser['organisation_id']]
|
||||
])->toArray();
|
||||
if (!isset($validIndividuals[$entity['owner_id']])) {
|
||||
throw new MethodNotAllowedException(__('Selected individual cannot be linked by the current user.'));
|
||||
}
|
||||
} else {
|
||||
if ($entity['owner_id'] !== $currentUser['id']) {
|
||||
throw new MethodNotAllowedException(__('Selected individual cannot be linked by the current user.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entity;
|
||||
};
|
||||
}
|
||||
$this->loadModel('Organisations');
|
||||
$this->loadModel('Individuals');
|
||||
$dropdownData = [
|
||||
'organisation' => $this->Organisations->find('list', [
|
||||
'sort' => ['name' => 'asc']
|
||||
]),
|
||||
'individual' => $this->Individuals->find('list', [
|
||||
'sort' => ['email' => 'asc']
|
||||
])
|
||||
'organisation' => $this->Organisations->find('list')->order(['name' => 'asc'])->where($orgConditions)->all()->toArray(),
|
||||
'individual' => $this->Individuals->find('list')->order(['email' => 'asc'])->where($individualConditions)->all()->toArray()
|
||||
];
|
||||
return $params;
|
||||
}
|
||||
|
||||
public function add()
|
||||
{
|
||||
$orgConditions = [];
|
||||
$individualConditions = [];
|
||||
$dropdownData = [];
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$params = [
|
||||
'redirect' => $this->referer()
|
||||
];
|
||||
$params = $this->buildBeforeSave($params, $currentUser, $orgConditions, $individualConditions, $dropdownData);
|
||||
$this->CRUD->add($params);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$this->set(compact('dropdownData'));
|
||||
$this->set('metaGroup', 'ContactDB');
|
||||
}
|
||||
|
||||
public function edit($id = false)
|
||||
{
|
||||
$orgConditions = [];
|
||||
$individualConditions = [];
|
||||
$dropdownData = [];
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$params = [
|
||||
'fields' => [
|
||||
'type', 'encryption_key', 'revoked'
|
||||
],
|
||||
'redirect' => $this->referer()
|
||||
];
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$params = $this->buildBeforeSave($params, $currentUser, $orgConditions, $individualConditions, $dropdownData);
|
||||
}
|
||||
$this->CRUD->edit($id, $params);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
|
@ -87,4 +152,16 @@ class EncryptionKeysController extends AppController
|
|||
$this->set('metaGroup', 'ContactDB');
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function view($id = false)
|
||||
{
|
||||
$this->CRUD->view($id, [
|
||||
'contain' => ['Individuals', 'Organisations']
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$this->set('metaGroup', 'ContactDB');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,12 @@ class InstanceController extends AppController
|
|||
usort($status, function($a, $b) {
|
||||
return strcmp($b['id'], $a['id']);
|
||||
});
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->RestResponse->viewData([
|
||||
'status' => $status,
|
||||
'updateAvailables' => $migrationStatus['updateAvailables'],
|
||||
], 'json');
|
||||
}
|
||||
$this->set('status', $status);
|
||||
$this->set('updateAvailables', $migrationStatus['updateAvailables']);
|
||||
}
|
||||
|
@ -139,6 +145,14 @@ class InstanceController extends AppController
|
|||
{
|
||||
$this->Settings = $this->getTableLocator()->get('Settings');
|
||||
$all = $this->Settings->getSettings(true);
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->RestResponse->viewData([
|
||||
'settingsProvider' => $all['settingsProvider'],
|
||||
'settings' => $all['settings'],
|
||||
'settingsFlattened' => $all['settingsFlattened'],
|
||||
'notices' => $all['notices'],
|
||||
], 'json');
|
||||
}
|
||||
$this->set('settingsProvider', $all['settingsProvider']);
|
||||
$this->set('settings', $all['settings']);
|
||||
$this->set('settingsFlattened', $all['settingsFlattened']);
|
||||
|
|
|
@ -340,6 +340,7 @@ class LocalToolsController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public function connectLocal($local_tool_id)
|
||||
{
|
||||
$params = [
|
||||
|
@ -355,10 +356,8 @@ class LocalToolsController extends AppController
|
|||
$params['target_tool_id'] = $postParams['target_tool_id'];
|
||||
$result = $this->LocalTools->encodeLocalConnection($params);
|
||||
// Send message to remote inbox
|
||||
debug($result);
|
||||
} else {
|
||||
$target_tools = $this->LocalTools->findConnectable($local_tool);
|
||||
debug($target_tools);
|
||||
if (empty($target_tools)) {
|
||||
throw new NotFoundException(__('No tools found to connect.'));
|
||||
}
|
||||
|
@ -369,4 +368,5 @@ class LocalToolsController extends AppController
|
|||
]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Controller\AppController;
|
|||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use \Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Http\Exception\BadRequestException;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Http\Exception\ForbiddenException;
|
||||
|
|
|
@ -16,10 +16,16 @@ class SharingGroupsController extends AppController
|
|||
|
||||
public function index()
|
||||
{
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$conditions = [];
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$conditions['SharingGroups.organisation_id'] = $currentUser['organisation_id'];
|
||||
}
|
||||
$this->CRUD->index([
|
||||
'contain' => $this->containFields,
|
||||
'filters' => $this->filterFields,
|
||||
'quickFilters' => $this->quickFilterFields
|
||||
'quickFilters' => $this->quickFilterFields,
|
||||
'conditions' => $conditions
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
|
@ -57,7 +63,12 @@ class SharingGroupsController extends AppController
|
|||
|
||||
public function edit($id = false)
|
||||
{
|
||||
$this->CRUD->edit($id);
|
||||
$params = [];
|
||||
$currentUser = $this->ACL->getUser();
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$params['conditions'] = ['organisation_id' => $currentUser['organisation_id']];
|
||||
}
|
||||
$this->CRUD->edit($id, $params);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
|
@ -201,11 +212,11 @@ class SharingGroupsController extends AppController
|
|||
$organisations = [];
|
||||
if (!empty($user['role']['perm_admin'])) {
|
||||
$organisations = $this->SharingGroups->Organisations->find('list')->order(['name' => 'ASC'])->toArray();
|
||||
} else if (!empty($user['individual']['organisations'])) {
|
||||
} else {
|
||||
$organisations = $this->SharingGroups->Organisations->find('list', [
|
||||
'sort' => ['name' => 'asc'],
|
||||
'conditions' => [
|
||||
'id IN' => array_values(\Cake\Utility\Hash::extract($user, 'individual.organisations.{n}.id'))
|
||||
'id' => $user['organisation_id']
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ use \Cake\Database\Expression\QueryExpression;
|
|||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Http\Exception\ForbiddenException;
|
||||
use Cake\Http\Exception\UnauthorizedException;
|
||||
|
||||
|
||||
class UserSettingsController extends AppController
|
||||
{
|
||||
|
@ -19,8 +21,12 @@ class UserSettingsController extends AppController
|
|||
public function index()
|
||||
{
|
||||
$conditions = [];
|
||||
$currentUser = $this->ACL->getUser();
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$conditions['user_id'] = $currentUser->id;
|
||||
}
|
||||
$this->CRUD->index([
|
||||
'conditions' => [],
|
||||
'conditions' => $conditions,
|
||||
'contain' => $this->containFields,
|
||||
'filters' => $this->filterFields,
|
||||
'quickFilters' => $this->quickFilterFields,
|
||||
|
@ -39,6 +45,9 @@ class UserSettingsController extends AppController
|
|||
|
||||
public function view($id)
|
||||
{
|
||||
if (!$this->isLoggedUserAllowedToEdit($id)) {
|
||||
throw new NotFoundException(__('Invalid {0}.', 'user setting'));
|
||||
}
|
||||
$this->CRUD->view($id, [
|
||||
'contain' => ['Users']
|
||||
]);
|
||||
|
@ -50,10 +59,13 @@ class UserSettingsController extends AppController
|
|||
|
||||
public function add($user_id = false)
|
||||
{
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$this->CRUD->add([
|
||||
'redirect' => ['action' => 'index', $user_id],
|
||||
'beforeSave' => function($data) use ($user_id) {
|
||||
$data['user_id'] = $user_id;
|
||||
'beforeSave' => function ($data) use ($currentUser) {
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$data['user_id'] = $currentUser->id;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
]);
|
||||
|
@ -61,10 +73,13 @@ class UserSettingsController extends AppController
|
|||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
$allUsers = $this->UserSettings->Users->find('list', ['keyField' => 'id', 'valueField' => 'username'])->order(['username' => 'ASC']);
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$allUsers->where(['id' => $currentUser->id]);
|
||||
$user_id = $currentUser->id;
|
||||
}
|
||||
$dropdownData = [
|
||||
'user' => $this->UserSettings->Users->find('list', [
|
||||
'sort' => ['username' => 'asc']
|
||||
]),
|
||||
'user' => $allUsers->all()->toArray(),
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
$this->set('user_id', $user_id);
|
||||
|
@ -75,6 +90,11 @@ class UserSettingsController extends AppController
|
|||
$entity = $this->UserSettings->find()->where([
|
||||
'id' => $id
|
||||
])->first();
|
||||
|
||||
if (!$this->isLoggedUserAllowedToEdit($entity)) {
|
||||
throw new NotFoundException(__('Invalid {0}.', 'user setting'));
|
||||
}
|
||||
|
||||
$entity = $this->CRUD->edit($id, [
|
||||
'redirect' => ['action' => 'index', $entity->user_id]
|
||||
]);
|
||||
|
@ -94,6 +114,9 @@ class UserSettingsController extends AppController
|
|||
|
||||
public function delete($id)
|
||||
{
|
||||
if (!$this->isLoggedUserAllowedToEdit($id)) {
|
||||
throw new NotFoundException(__('Invalid {0}.', 'user setting'));
|
||||
}
|
||||
$this->CRUD->delete($id);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
|
@ -160,7 +183,7 @@ class UserSettingsController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
public function getBookmarks($forSidebar=false)
|
||||
public function getBookmarks($forSidebar = false)
|
||||
{
|
||||
$bookmarks = $this->UserSettings->getSettingByName($this->ACL->getUser(), $this->UserSettings->BOOKMARK_SETTING_NAME);
|
||||
$bookmarks = json_decode($bookmarks['value'], true);
|
||||
|
@ -200,4 +223,29 @@ class UserSettingsController extends AppController
|
|||
$this->set('user_id', $this->ACL->getUser()->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* isLoggedUserAllowedToEdit
|
||||
*
|
||||
* @param int|\App\Model\Entity\UserSetting $setting
|
||||
* @return boolean
|
||||
*/
|
||||
private function isLoggedUserAllowedToEdit($setting): bool
|
||||
{
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$isAllowed = false;
|
||||
if (!empty($currentUser['role']['perm_admin'])) {
|
||||
$isAllowed = true;
|
||||
} else {
|
||||
if (is_numeric($setting)) {
|
||||
$setting = $this->UserSettings->find()->where([
|
||||
'id' => $setting
|
||||
])->first();
|
||||
if (empty($setting)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$isAllowed = $setting->user_id == $currentUser->id;
|
||||
}
|
||||
return $isAllowed;
|
||||
}
|
||||
}
|
|
@ -7,20 +7,27 @@ use Cake\Utility\Text;
|
|||
use Cake\ORM\TableRegistry;
|
||||
use \Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Http\Exception\UnauthorizedException;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Core\Configure;
|
||||
|
||||
class UsersController extends AppController
|
||||
{
|
||||
public $filterFields = ['Individuals.uuid', 'username', 'Individuals.email', 'Individuals.first_name', 'Individuals.last_name'];
|
||||
public $filterFields = ['Individuals.uuid', 'username', 'Individuals.email', 'Individuals.first_name', 'Individuals.last_name', 'Organisations.name'];
|
||||
public $quickFilterFields = ['Individuals.uuid', ['username' => true], ['Individuals.first_name' => true], ['Individuals.last_name' => true], 'Individuals.email'];
|
||||
public $containFields = ['Individuals', 'Roles', 'UserSettings'];
|
||||
public $containFields = ['Individuals', 'Roles', 'UserSettings', 'Organisations'];
|
||||
|
||||
public function index()
|
||||
{
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$conditions = [];
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$conditions['organisation_id'] = $currentUser['organisation_id'];
|
||||
}
|
||||
$this->CRUD->index([
|
||||
'contain' => $this->containFields,
|
||||
'filters' => $this->filterFields,
|
||||
'quickFilters' => $this->quickFilterFields,
|
||||
'conditions' => $conditions
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
|
@ -31,8 +38,12 @@ class UsersController extends AppController
|
|||
|
||||
public function add()
|
||||
{
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$this->CRUD->add([
|
||||
'beforeSave' => function($data) {
|
||||
'beforeSave' => function($data) use ($currentUser) {
|
||||
if (!$currentUser['role']['perm_admin']) {
|
||||
$data['organisation_id'] = $currentUser['organisation_id'];
|
||||
}
|
||||
$this->Users->enrollUserRouter($data);
|
||||
return $data;
|
||||
}
|
||||
|
@ -41,12 +52,28 @@ class UsersController extends AppController
|
|||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
/*
|
||||
$alignments = $this->Users->Individuals->Alignments->find('list', [
|
||||
//'keyField' => 'id',
|
||||
'valueField' => 'organisation_id',
|
||||
'groupField' => 'individual_id'
|
||||
])->toArray();
|
||||
$alignments = array_map(function($value) { return array_values($value); }, $alignments);
|
||||
*/
|
||||
$org_conditions = [];
|
||||
if (empty($currentUser['role']['perm_admin'])) {
|
||||
$org_conditions = ['id' => $currentUser['organisation_id']];
|
||||
}
|
||||
$dropdownData = [
|
||||
'role' => $this->Users->Roles->find('list', [
|
||||
'sort' => ['name' => 'asc']
|
||||
]),
|
||||
'individual' => $this->Users->Individuals->find('list', [
|
||||
'sort' => ['email' => 'asc']
|
||||
]),
|
||||
'organisation' => $this->Users->Organisations->find('list', [
|
||||
'sort' => ['name' => 'asc'],
|
||||
'conditions' => $org_conditions
|
||||
])
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
|
@ -59,7 +86,7 @@ class UsersController extends AppController
|
|||
$id = $this->ACL->getUser()['id'];
|
||||
}
|
||||
$this->CRUD->view($id, [
|
||||
'contain' => ['Individuals' => ['Alignments' => 'Organisations'], 'Roles']
|
||||
'contain' => ['Individuals' => ['Alignments' => 'Organisations'], 'Roles', 'Organisations']
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
|
@ -70,9 +97,18 @@ class UsersController extends AppController
|
|||
|
||||
public function edit($id = false)
|
||||
{
|
||||
if (empty($id) || empty($this->ACL->getUser()['role']['perm_admin'])) {
|
||||
$id = $this->ACL->getUser()['id'];
|
||||
$currentUser = $this->ACL->getUser();
|
||||
if (empty($id)) {
|
||||
$id = $currentUser['id'];
|
||||
} else {
|
||||
$id = intval($id);
|
||||
if ((empty($currentUser['role']['perm_org_admin']) && empty($currentUser['role']['perm_admin']))) {
|
||||
if ($id !== $currentUser['id']) {
|
||||
throw new MethodNotAllowedException(__('You are not authorised to edit that user.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$params = [
|
||||
'get' => [
|
||||
'fields' => [
|
||||
|
@ -83,11 +119,15 @@ class UsersController extends AppController
|
|||
'password'
|
||||
],
|
||||
'fields' => [
|
||||
'id', 'individual_id', 'username', 'disabled', 'password', 'confirm_password'
|
||||
'password', 'confirm_password'
|
||||
]
|
||||
];
|
||||
if (!empty($this->ACL->getUser()['role']['perm_admin'])) {
|
||||
$params['fields'][] = 'individual_id';
|
||||
$params['fields'][] = 'username';
|
||||
$params['fields'][] = 'role_id';
|
||||
$params['fields'][] = 'organisation_id';
|
||||
$params['fields'][] = 'disabled';
|
||||
}
|
||||
$this->CRUD->edit($id, $params);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
|
@ -100,6 +140,9 @@ class UsersController extends AppController
|
|||
]),
|
||||
'individual' => $this->Users->Individuals->find('list', [
|
||||
'sort' => ['email' => 'asc']
|
||||
]),
|
||||
'organisation' => $this->Users->Organisations->find('list', [
|
||||
'sort' => ['name' => 'asc']
|
||||
])
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
|
@ -130,11 +173,27 @@ class UsersController extends AppController
|
|||
{
|
||||
$result = $this->Authentication->getResult();
|
||||
// If the user is logged in send them away.
|
||||
$logModel = $this->Users->auditLogs();
|
||||
if ($result->isValid()) {
|
||||
$user = $logModel->userInfo();
|
||||
$logModel->insert([
|
||||
'request_action' => 'login',
|
||||
'model' => 'Users',
|
||||
'model_id' => $user['id'],
|
||||
'model_title' => $user['name'],
|
||||
'changed' => []
|
||||
]);
|
||||
$target = $this->Authentication->getLoginRedirect() ?? '/instance/home';
|
||||
return $this->redirect($target);
|
||||
}
|
||||
if ($this->request->is('post') && !$result->isValid()) {
|
||||
$logModel->insert([
|
||||
'request_action' => 'login_fail',
|
||||
'model' => 'Users',
|
||||
'model_id' => 0,
|
||||
'model_title' => 'unknown_user',
|
||||
'changed' => []
|
||||
]);
|
||||
$this->Flash->error(__('Invalid username or password'));
|
||||
}
|
||||
$this->viewBuilder()->setLayout('login');
|
||||
|
@ -144,6 +203,15 @@ class UsersController extends AppController
|
|||
{
|
||||
$result = $this->Authentication->getResult();
|
||||
if ($result->isValid()) {
|
||||
$logModel = $this->Users->auditLogs();
|
||||
$user = $logModel->userInfo();
|
||||
$logModel->insert([
|
||||
'request_action' => 'logout',
|
||||
'model' => 'Users',
|
||||
'model_id' => $user['id'],
|
||||
'model_title' => $user['name'],
|
||||
'changed' => []
|
||||
]);
|
||||
$this->Authentication->logout();
|
||||
$this->Flash->success(__('Goodbye.'));
|
||||
return $this->redirect(\Cake\Routing\Router::url('/users/login'));
|
||||
|
@ -162,7 +230,7 @@ class UsersController extends AppController
|
|||
|
||||
public function register()
|
||||
{
|
||||
if (empty(Configure::read('Cerebrate')['security.registration.self-registration'])) {
|
||||
if (empty(Configure::read('security.registration.self-registration'))) {
|
||||
throw new UnauthorizedException(__('User self-registration is not open.'));
|
||||
}
|
||||
if ($this->request->is('post')) {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace CommonConnectorTools;
|
||||
use Cake\ORM\Locator\LocatorAwareTrait;
|
||||
use Cake\Log\Log;
|
||||
use Cake\Log\Engine\FileLog;
|
||||
|
||||
class CommonConnectorTools
|
||||
{
|
||||
|
@ -20,6 +22,38 @@ class CommonConnectorTools
|
|||
const STATE_CANCELLED = 'Request cancelled';
|
||||
const STATE_DECLINED = 'Request declined by remote';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (empty(Log::getConfig("LocalToolDebug{$this->connectorName}"))) {
|
||||
Log::setConfig("LocalToolDebug{$this->connectorName}", [
|
||||
'className' => FileLog::class,
|
||||
'path' => LOGS,
|
||||
'file' => "{$this->connectorName}-debug",
|
||||
'scopes' => [$this->connectorName],
|
||||
'levels' => ['notice', 'info', 'debug'],
|
||||
]);
|
||||
}
|
||||
if (empty(Log::getConfig("LocalToolError{$this->connectorName}"))) {
|
||||
Log::setConfig("LocalToolError{$this->connectorName}", [
|
||||
'className' => FileLog::class,
|
||||
'path' => LOGS,
|
||||
'file' => "{$this->connectorName}-error",
|
||||
'scopes' => [$this->connectorName],
|
||||
'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function logDebug($message)
|
||||
{
|
||||
Log::debug($message, [$this->connectorName]);
|
||||
}
|
||||
|
||||
protected function logError($message, $scope=[])
|
||||
{
|
||||
Log::error($message, [$this->connectorName]);
|
||||
}
|
||||
|
||||
public function addExposedFunction(string $functionName): void
|
||||
{
|
||||
$this->exposedFunctions[] = $functionName;
|
||||
|
|
|
@ -122,6 +122,11 @@ class MispConnector extends CommonConnectorTools
|
|||
'type' => 'boolean'
|
||||
],
|
||||
];
|
||||
public $settingsPlaceholder = [
|
||||
'url' => 'https://your.misp.intance',
|
||||
'authkey' => '',
|
||||
'skip_ssl' => '0',
|
||||
];
|
||||
|
||||
public function addSettingValidatorRules($validator)
|
||||
{
|
||||
|
@ -183,6 +188,7 @@ class MispConnector extends CommonConnectorTools
|
|||
$settings = json_decode($connection->settings, true);
|
||||
$http = $this->genHTTPClient($connection, $options);
|
||||
$url = sprintf('%s%s', $settings['url'], $relativeURL);
|
||||
$this->logDebug(sprintf('%s %s %s', __('Posting data') . PHP_EOL, "POST {$url}" . PHP_EOL, json_encode($data)));
|
||||
return $http->post($url, $data, $options);
|
||||
}
|
||||
|
||||
|
@ -234,14 +240,18 @@ class MispConnector extends CommonConnectorTools
|
|||
if (!empty($params['softError'])) {
|
||||
return $response;
|
||||
}
|
||||
throw new NotFoundException(__('Could not retrieve the requested resource.'));
|
||||
$errorMsg = __('Could not post to the requested resource for `{0}`. Remote returned:', $url) . PHP_EOL . $response->getStringBody();
|
||||
$this->logError($errorMsg);
|
||||
throw new NotFoundException($errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private function postData(string $url, array $params): Response
|
||||
{
|
||||
if (empty($params['connection'])) {
|
||||
throw new NotFoundException(__('No connection object received.'));
|
||||
$errorMsg = __('No connection object received.');
|
||||
$this->logError($errorMsg);
|
||||
throw new NotFoundException($errorMsg);
|
||||
}
|
||||
$url = $this->urlAppendParams($url, $params);
|
||||
if (!is_string($params['body'])) {
|
||||
|
@ -251,7 +261,9 @@ class MispConnector extends CommonConnectorTools
|
|||
if ($response->isOk()) {
|
||||
return $response;
|
||||
} else {
|
||||
throw new NotFoundException(__('Could not post to the requested resource. Remote returned:') . PHP_EOL . $response->getStringBody());
|
||||
$errorMsg = __('Could not post to the requested resource for `{0}`. Remote returned:', $url) . PHP_EOL . $response->getStringBody();
|
||||
$this->logError($errorMsg);
|
||||
throw new NotFoundException($errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,54 +321,55 @@ class MispConnector extends CommonConnectorTools
|
|||
'additionalUrlParams' => $urlParams
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'name' => 'Setting',
|
||||
'sort' => 'setting',
|
||||
'data_path' => 'setting',
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'name' => 'Setting',
|
||||
'sort' => 'setting',
|
||||
'data_path' => 'setting',
|
||||
[
|
||||
'name' => 'Criticality',
|
||||
'sort' => 'level',
|
||||
'data_path' => 'level',
|
||||
'arrayData' => [
|
||||
0 => 'Critical',
|
||||
1 => 'Recommended',
|
||||
2 => 'Optional'
|
||||
],
|
||||
[
|
||||
'name' => 'Criticality',
|
||||
'sort' => 'level',
|
||||
'data_path' => 'level',
|
||||
'arrayData' => [
|
||||
0 => 'Critical',
|
||||
1 => 'Recommended',
|
||||
2 => 'Optional'
|
||||
],
|
||||
'element' => 'array_lookup_field'
|
||||
],
|
||||
[
|
||||
'name' => __('Value'),
|
||||
'sort' => 'value',
|
||||
'data_path' => 'value',
|
||||
'options' => 'options'
|
||||
],
|
||||
[
|
||||
'name' => __('Type'),
|
||||
'sort' => 'type',
|
||||
'data_path' => 'type',
|
||||
],
|
||||
[
|
||||
'name' => __('Error message'),
|
||||
'sort' => 'errorMessage',
|
||||
'data_path' => 'errorMessage',
|
||||
]
|
||||
'element' => 'array_lookup_field'
|
||||
],
|
||||
'title' => false,
|
||||
'description' => false,
|
||||
'pull' => 'right',
|
||||
'actions' => [
|
||||
[
|
||||
'open_modal' => '/localTools/action/' . h($params['connection']['id']) . '/modifySettingAction?setting={{0}}',
|
||||
'modal_params_data_path' => ['setting'],
|
||||
'icon' => 'download',
|
||||
'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/ServerSettingsAction'
|
||||
]
|
||||
[
|
||||
'name' => __('Value'),
|
||||
'sort' => 'value',
|
||||
'data_path' => 'value',
|
||||
'options' => 'options'
|
||||
],
|
||||
[
|
||||
'name' => __('Type'),
|
||||
'sort' => 'type',
|
||||
'data_path' => 'type',
|
||||
],
|
||||
[
|
||||
'name' => __('Error message'),
|
||||
'sort' => 'errorMessage',
|
||||
'data_path' => 'errorMessage',
|
||||
]
|
||||
],
|
||||
'title' => false,
|
||||
'description' => false,
|
||||
'pull' => 'right',
|
||||
'actions' => [
|
||||
[
|
||||
'open_modal' => '/localTools/action/' . h($params['connection']['id']) . '/modifySettingAction?setting={{0}}',
|
||||
'modal_params_data_path' => ['setting'],
|
||||
'icon' => 'download',
|
||||
'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/ServerSettingsAction'
|
||||
]
|
||||
]
|
||||
];
|
||||
]
|
||||
];
|
||||
if (!empty($params['quickFilter'])) {
|
||||
$needle = strtolower($params['quickFilter']);
|
||||
foreach ($finalSettings['data']['data'] as $k => $v) {
|
||||
|
|
|
@ -46,6 +46,22 @@ class SkeletonConnector extends CommonConnectorTools
|
|||
'redirect' => 'serverSettingsAction'
|
||||
]
|
||||
];
|
||||
public $settings = [
|
||||
'url' => [
|
||||
'type' => 'text'
|
||||
],
|
||||
'authkey' => [
|
||||
'type' => 'text'
|
||||
],
|
||||
'skip_ssl' => [
|
||||
'type' => 'boolean'
|
||||
],
|
||||
];
|
||||
public $settingsPlaceholder = [
|
||||
'url' => 'https://your.url',
|
||||
'authkey' => '',
|
||||
'skip_ssl' => '0',
|
||||
];
|
||||
|
||||
public function health(Object $connection): array
|
||||
{
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
namespace App\Model\Behavior;
|
||||
|
||||
use ArrayObject;
|
||||
use Cake\Datasource\EntityInterface;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\ORM\Behavior;
|
||||
use Cake\ORM\Entity;
|
||||
use Cake\ORM\Query;
|
||||
use Cake\Utility\Text;
|
||||
use Cake\Utility\Security;
|
||||
use \Cake\Http\Session;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use App\Model\Table\AuditLogTable;
|
||||
|
||||
class AuditLogBehavior extends Behavior
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
/** @var array|null */
|
||||
private $old;
|
||||
|
||||
/** @var AuditLog|null */
|
||||
private $AuditLogs;
|
||||
|
||||
// Hash is faster that in_array
|
||||
private $skipFields = [
|
||||
'id' => true,
|
||||
'lastpushedid' => true,
|
||||
'timestamp' => true,
|
||||
'revision' => true,
|
||||
'modified' => true,
|
||||
'date_modified' => true, // User
|
||||
'current_login' => true, // User
|
||||
'last_login' => true, // User
|
||||
'newsread' => true, // User
|
||||
'proposal_email_lock' => true, // Event
|
||||
];
|
||||
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
|
||||
{
|
||||
$fields = $entity->extract($entity->getVisible(), true);
|
||||
$skipFields = $this->skipFields;
|
||||
$fieldsToFetch = array_filter($fields, function($key) use ($skipFields) {
|
||||
return strpos($key, '_') !== 0 && !isset($skipFields[$key]);
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
// Do not fetch old version when just few fields will be fetched
|
||||
$fieldToFetch = [];
|
||||
if (!empty($options['fieldList'])) {
|
||||
foreach ($options['fieldList'] as $field) {
|
||||
if (!isset($this->skipFields[$field])) {
|
||||
$fieldToFetch[] = $field;
|
||||
}
|
||||
}
|
||||
if (empty($fieldToFetch)) {
|
||||
$this->old = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ($entity->id) {
|
||||
$this->old = $this->_table->find()->where(['id' => $entity->id])->contain($fieldToFetch)->first();
|
||||
} else {
|
||||
$this->old = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
|
||||
{
|
||||
if ($entity->id) {
|
||||
$id = $entity->id;
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
|
||||
if ($entity->isNew()) {
|
||||
$action = $entity->getConstant('ACTION_ADD');
|
||||
} else {
|
||||
$action = $entity->getConstant('ACTION_EDIT');
|
||||
if (isset($entity['deleted'])) {
|
||||
if ($entity['deleted']) {
|
||||
$action = $entity->getConstant('ACTION_SOFT_DELETE');
|
||||
} else if (!$entity['deleted'] && $this->old['deleted']) {
|
||||
$action = $entity->getConstant('ACTION_UNDELETE');
|
||||
}
|
||||
}
|
||||
}
|
||||
$changedFields = $this->changedFields($entity, isset($options['fieldList']) ? $options['fieldList'] : null);
|
||||
if (empty($changedFields)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$modelTitleField = $this->_table->getDisplayField();
|
||||
if (is_callable($modelTitleField)) {
|
||||
$modelTitle = $modelTitleField($entity, isset($this->old) ? $this->old : []);
|
||||
} else if (isset($entity[$modelTitleField])) {
|
||||
$modelTitle = $entity[$modelTitleField];
|
||||
} else if ($this->old[$modelTitleField]) {
|
||||
$modelTitle = $this->old[$modelTitleField];
|
||||
}
|
||||
$this->auditLogs()->insert([
|
||||
'request_action' => $action,
|
||||
'model' => $entity->getSource(),
|
||||
'model_id' => $id,
|
||||
'model_title' => $modelTitle,
|
||||
'changed' => $changedFields
|
||||
]);
|
||||
}
|
||||
|
||||
public function beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)
|
||||
{
|
||||
$this->old = $this->_table->find()->where(['id' => $entity->id])->first();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)
|
||||
{
|
||||
$modelTitleField = $this->_table->getDisplayField();
|
||||
if (is_callable($modelTitleField)) {
|
||||
$modelTitle = $modelTitleField($entity, []);
|
||||
} else if (isset($entity[$modelTitleField])) {
|
||||
$modelTitle = $entity[$modelTitleField];
|
||||
}
|
||||
|
||||
$this->auditLogs()->insert([
|
||||
'request_action' => $entity->getConstant('ACTION_DELETE'),
|
||||
'model' => $entity->getSource(),
|
||||
'model_id' => $this->old->id,
|
||||
'model_title' => $modelTitle,
|
||||
'changed' => $this->changedFields($entity)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Model $model
|
||||
* @param array|null $fieldsToSave
|
||||
* @return array
|
||||
*/
|
||||
private function changedFields(EntityInterface $entity, $fieldsToSave = null)
|
||||
{
|
||||
$dbFields = $this->_table->getSchema()->typeMap();
|
||||
$changedFields = [];
|
||||
foreach ($entity->extract($entity->getVisible()) as $key => $value) {
|
||||
if (isset($this->skipFields[$key])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($dbFields[$key])) {
|
||||
continue;
|
||||
}
|
||||
if ($fieldsToSave && !in_array($key, $fieldsToSave, true)) {
|
||||
continue;
|
||||
}
|
||||
if (isset($entity[$key]) && isset($this->old[$key])) {
|
||||
$old = $this->old[$key];
|
||||
} else {
|
||||
$old = null;
|
||||
}
|
||||
// Normalize
|
||||
if (is_bool($old)) {
|
||||
$old = $old ? 1 : 0;
|
||||
}
|
||||
if (is_bool($value)) {
|
||||
$value = $value ? 1 : 0;
|
||||
}
|
||||
$dbType = $dbFields[$key];
|
||||
if ($dbType === 'integer' || $dbType === 'tinyinteger' || $dbType === 'biginteger' || $dbType === 'boolean') {
|
||||
$value = (int)$value;
|
||||
if ($old !== null) {
|
||||
$old = (int)$old;
|
||||
}
|
||||
}
|
||||
if ($value == $old) {
|
||||
continue;
|
||||
}
|
||||
if ($key === 'password' || $key === 'authkey') {
|
||||
$value = '*****';
|
||||
if ($old !== null) {
|
||||
$old = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($old === null) {
|
||||
$changedFields[$key] = $value;
|
||||
} else {
|
||||
$changedFields[$key] = [$old, $value];
|
||||
}
|
||||
}
|
||||
return $changedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AuditLogs
|
||||
*/
|
||||
public function auditLogs()
|
||||
{
|
||||
if ($this->AuditLogs === null) {
|
||||
$this->AuditLogs = TableRegistry::getTableLocator()->get('AuditLogs');
|
||||
}
|
||||
return $this->AuditLogs;
|
||||
}
|
||||
|
||||
public function log()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -98,7 +98,7 @@ class AuthKeycloakBehavior extends Behavior
|
|||
{
|
||||
$individual = $this->_table->Individuals->find()->where(
|
||||
['id' => $data['individual_id']]
|
||||
)->contain(['Organisations'])->first();
|
||||
)->first();
|
||||
$roleConditions = [
|
||||
'id' => $data['role_id']
|
||||
];
|
||||
|
@ -106,10 +106,9 @@ class AuthKeycloakBehavior extends Behavior
|
|||
$roleConditions['name'] = Configure::read('keycloak.default_role_name');
|
||||
}
|
||||
$role = $this->_table->Roles->find()->where($roleConditions)->first();
|
||||
$orgs = [];
|
||||
foreach ($individual['organisations'] as $org) {
|
||||
$orgs[] = $org['uuid'];
|
||||
}
|
||||
$org = $this->_table->Organisations->find()->where([
|
||||
['id' => $data['organisation_id']]
|
||||
])->first();
|
||||
$token = $this->getAdminAccessToken();
|
||||
$keyCloakUser = [
|
||||
'firstName' => $individual['first_name'],
|
||||
|
@ -118,7 +117,7 @@ class AuthKeycloakBehavior extends Behavior
|
|||
'email' => $individual['email'],
|
||||
'attributes' => [
|
||||
'role_name' => empty($role['name']) ? Configure::read('keycloak.default_role_name') : $role['name'],
|
||||
'org_uuid' => empty($orgs[0]) ? '' : $orgs[0]
|
||||
'org_uuid' => $org['uuid']
|
||||
]
|
||||
];
|
||||
$keycloakConfig = Configure::read('keycloak');
|
||||
|
|
|
@ -6,6 +6,28 @@ use Cake\ORM\Entity;
|
|||
|
||||
class AppModel extends Entity
|
||||
{
|
||||
const BROTLI_HEADER = "\xce\xb2\xcf\x81";
|
||||
const BROTLI_MIN_LENGTH = 200;
|
||||
|
||||
const ACTION_ADD = 'add',
|
||||
ACTION_EDIT = 'edit',
|
||||
ACTION_SOFT_DELETE = 'soft_delete',
|
||||
ACTION_DELETE = 'delete',
|
||||
ACTION_UNDELETE = 'undelete',
|
||||
ACTION_TAG = 'tag',
|
||||
ACTION_TAG_LOCAL = 'tag_local',
|
||||
ACTION_REMOVE_TAG = 'remove_tag',
|
||||
ACTION_REMOVE_TAG_LOCAL = 'remove_local_tag',
|
||||
ACTION_LOGIN = 'login',
|
||||
ACTION_LOGIN_FAIL = 'login_fail',
|
||||
ACTION_LOGOUT = 'logout';
|
||||
|
||||
|
||||
public function getConstant($name)
|
||||
{
|
||||
return constant('self::' . $name);
|
||||
}
|
||||
|
||||
public function getAccessibleFieldForNew(): array
|
||||
{
|
||||
return $this->_accessibleOnNew ?? [];
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Entity;
|
||||
|
||||
use App\Model\Entity\AppModel;
|
||||
use Cake\ORM\Entity;
|
||||
use Cake\Core\Configure;
|
||||
|
||||
class AuditLog extends AppModel
|
||||
{
|
||||
private $compressionEnabled = false;
|
||||
|
||||
public function __construct(array $properties = [], array $options = [])
|
||||
{
|
||||
$this->compressionEnabled = Configure::read('Cerebrate.log_compress') && function_exists('brotli_compress');
|
||||
parent::__construct($properties, $options);
|
||||
}
|
||||
|
||||
protected function _getTitle(): String
|
||||
{
|
||||
return $this->generateUserFriendlyTitle($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $change
|
||||
* @return array|string
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function decodeChange($change)
|
||||
{
|
||||
if (substr($change, 0, 4) === self::BROTLI_HEADER) {
|
||||
if (function_exists('brotli_uncompress')) {
|
||||
$change = brotli_uncompress(substr($change, 4));
|
||||
if ($change === false) {
|
||||
return 'Compressed';
|
||||
}
|
||||
} else {
|
||||
return 'Compressed';
|
||||
}
|
||||
}
|
||||
return json_decode($change, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $auditLog
|
||||
* @return string
|
||||
*/
|
||||
public function generateUserFriendlyTitle($auditLog)
|
||||
{
|
||||
if (in_array($auditLog['request_action'], [self::ACTION_TAG, self::ACTION_TAG_LOCAL, self::ACTION_REMOVE_TAG, self::ACTION_REMOVE_TAG_LOCAL], true)) {
|
||||
$attached = ($auditLog['request_action'] === self::ACTION_TAG || $auditLog['request_action'] === self::ACTION_TAG_LOCAL);
|
||||
$local = ($auditLog['request_action'] === self::ACTION_TAG_LOCAL || $auditLog['request_action'] === self::ACTION_REMOVE_TAG_LOCAL) ? __('local') : __('global');
|
||||
if ($attached) {
|
||||
return __('Attached %s tag "%s" to %s #%s', $local, $auditLog['model_title'], strtolower($auditLog['model']), $auditLog['model_id']);
|
||||
} else {
|
||||
return __('Detached %s tag "%s" from %s #%s', $local, $auditLog['model_title'], strtolower($auditLog['model']), $auditLog['model_id']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$title = "{$auditLog['model']} #{$auditLog['model_id']}";
|
||||
|
||||
if (isset($auditLog['model_title']) && $auditLog['model_title']) {
|
||||
$title .= ": {$auditLog['model_title']}";
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
}
|
|
@ -10,10 +10,5 @@ class Organisation extends AppModel
|
|||
protected $_accessible = [
|
||||
'*' => true,
|
||||
'id' => false,
|
||||
'uuid' => false,
|
||||
];
|
||||
|
||||
protected $_accessibleOnNew = [
|
||||
'uuid' => true,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Entity;
|
||||
|
||||
use App\Model\Entity\AppModel;
|
||||
use Cake\ORM\Entity;
|
||||
|
||||
class Outbox extends AppModel
|
||||
{
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ class AlignmentsTable extends AppTable
|
|||
{
|
||||
parent::initialize($config);
|
||||
$this->belongsTo('Individuals');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->belongsTo('Organisations');
|
||||
$this->addBehavior('Timestamp');
|
||||
}
|
||||
|
|
|
@ -107,8 +107,9 @@ class AppTable extends Table
|
|||
$statistics['amount'] = $table->find()->all()->count();
|
||||
if ($table->behaviors()->has('Timestamp') && $includeTimeline) {
|
||||
$statistics['timeline'] = $this->buildTimeline($table, $days, $field);
|
||||
$statistics['variation'] = $table->find()->where(["{$field} >" => new \DateTime("-{$days} days")])->all()->count();
|
||||
-$statistics['amount'];
|
||||
$startCount = $table->find()->where(["{$field} >" => new \DateTime("-{$days} days")])->all()->count();
|
||||
$endCount = $statistics['amount'];
|
||||
$statistics['variation'] = $startCount - $endCount;
|
||||
} else {
|
||||
$statistics['timeline'] = [];
|
||||
$statistics['variation'] = 0;
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
<?php
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\Validation\Validator;
|
||||
use Cake\Datasource\EntityInterface;
|
||||
use Cake\Event\Event;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\Auth\DefaultPasswordHasher;
|
||||
use Cake\Utility\Security;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Routing\Router;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use ArrayObject;
|
||||
|
||||
/**
|
||||
* @property Event $Event
|
||||
* @property User $User
|
||||
* @property Organisation $Organisation
|
||||
*/
|
||||
class AuditLogsTable extends AppTable
|
||||
{
|
||||
const BROTLI_HEADER = "\xce\xb2\xcf\x81";
|
||||
const BROTLI_MIN_LENGTH = 200;
|
||||
|
||||
const REQUEST_TYPE_DEFAULT = 0,
|
||||
REQUEST_TYPE_API = 1,
|
||||
REQUEST_TYPE_CLI = 2;
|
||||
|
||||
/** @var array|null */
|
||||
private $user = null;
|
||||
|
||||
/** @var bool */
|
||||
private $compressionEnabled;
|
||||
|
||||
/**
|
||||
* Null when not defined, false when not enabled
|
||||
* @var Syslog|null|false
|
||||
*/
|
||||
private $syslog;
|
||||
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('Timestamp', [
|
||||
'Model.beoreSave' => [
|
||||
'created_at' => 'new'
|
||||
]
|
||||
]);
|
||||
$this->belongsTo('Users');
|
||||
$this->setDisplayField('info');
|
||||
$this->compressionEnabled = Configure::read('Cerebrate.log_new_audit_compress') && function_exists('brotli_compress');
|
||||
}
|
||||
|
||||
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
|
||||
{
|
||||
if (!isset($data['request_ip'])) {
|
||||
$ipHeader = 'REMOTE_ADDR';
|
||||
if (isset($_SERVER[$ipHeader])) {
|
||||
$data['request_ip'] = $_SERVER[$ipHeader];
|
||||
} else {
|
||||
$data['request_ip'] = '127.0.0.1';
|
||||
}
|
||||
}
|
||||
foreach (['user_id', 'request_type', 'authkey_id'] as $field) {
|
||||
if (!isset($data[$field])) {
|
||||
if (!isset($userInfo)) {
|
||||
$userInfo = $this->userInfo();
|
||||
}
|
||||
if (!empty($userInfo[$field])) {
|
||||
$data[$field] = $userInfo[$field];
|
||||
} else {
|
||||
$data[$field] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($data['request_id'] ) && isset($_SERVER['HTTP_X_REQUEST_ID'])) {
|
||||
$data['request_id'] = $_SERVER['HTTP_X_REQUEST_ID'];
|
||||
}
|
||||
|
||||
// Truncate request_id
|
||||
if (isset($data['request_id']) && strlen($data['request_id']) > 255) {
|
||||
$data['request_id'] = substr($data['request_id'], 0, 255);
|
||||
}
|
||||
|
||||
// Truncate model title
|
||||
if (isset($data['model_title']) && mb_strlen($data['model_title']) > 255) {
|
||||
$data['model_title'] = mb_substr($data['model_title'], 0, 252) . '...';
|
||||
}
|
||||
|
||||
if (isset($data['changed'])) {
|
||||
$changed = json_encode($data['changed'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($this->compressionEnabled && strlen($changed) >= self::BROTLI_MIN_LENGTH) {
|
||||
$changed = self::BROTLI_HEADER . brotli_compress($changed, 4, BROTLI_TEXT);
|
||||
}
|
||||
$data['changed'] = $changed;
|
||||
}
|
||||
}
|
||||
|
||||
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
|
||||
{
|
||||
$entity->request_ip = inet_pton($entity->request_ip);
|
||||
$this->logData($entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
private function logData(EntityInterface $entity)
|
||||
{
|
||||
if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_audit_notifications_enable')) {
|
||||
$pubSubTool = $this->getPubSubTool();
|
||||
$pubSubTool->publish($data, 'audit', 'log');
|
||||
}
|
||||
|
||||
//$this->publishKafkaNotification('audit', $data, 'log');
|
||||
|
||||
if (Configure::read('Plugin.ElasticSearch_logging_enable')) {
|
||||
// send off our logs to distributed /dev/null
|
||||
$logIndex = Configure::read("Plugin.ElasticSearch_log_index");
|
||||
$elasticSearchClient = $this->getElasticSearchTool();
|
||||
$elasticSearchClient->pushDocument($logIndex, "log", $data);
|
||||
}
|
||||
|
||||
// write to syslogd as well if enabled
|
||||
if ($this->syslog === null) {
|
||||
if (Configure::read('Security.syslog')) {
|
||||
$options = [];
|
||||
$syslogToStdErr = Configure::read('Security.syslog_to_stderr');
|
||||
if ($syslogToStdErr !== null) {
|
||||
$options['to_stderr'] = $syslogToStdErr;
|
||||
}
|
||||
$syslogIdent = Configure::read('Security.syslog_ident');
|
||||
if ($syslogIdent) {
|
||||
$options['ident'] = $syslogIdent;
|
||||
}
|
||||
$this->syslog = new SysLog($options);
|
||||
} else {
|
||||
$this->syslog = false;
|
||||
}
|
||||
}
|
||||
if ($this->syslog) {
|
||||
$entry = $data['request_action'];
|
||||
$title = $entity->generateUserFriendlyTitle();
|
||||
if ($title) {
|
||||
$entry .= " -- $title";
|
||||
}
|
||||
$this->syslog->write('info', $entry);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function userInfo()
|
||||
{
|
||||
if ($this->user !== null) {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
$this->user = ['id' => 0, /*'org_id' => 0, */'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT, 'name' => ''];
|
||||
|
||||
$isShell = (php_sapi_name() === 'cli');
|
||||
if ($isShell) {
|
||||
// do not start session for shell commands and fetch user info from configuration
|
||||
$this->user['request_type'] = self::REQUEST_TYPE_CLI;
|
||||
$currentUserId = Configure::read('CurrentUserId');
|
||||
if (!empty($currentUserId)) {
|
||||
$this->user['id'] = $currentUserId;
|
||||
$userFromDb = $this->Users->find()->where(['id' => $currentUserId])->first();
|
||||
$this->user['name'] = $userFromDb['name'];
|
||||
$this->user['org_id'] = $userFromDb['org_id'];
|
||||
}
|
||||
} else {
|
||||
$authUser = Router::getRequest()->getSession()->read('authUser');
|
||||
if (!empty($authUser)) {
|
||||
$this->user['id'] = $authUser['id'];
|
||||
$this->user['user_id'] = $authUser['id'];
|
||||
$this->user['name'] = $authUser['name'];
|
||||
//$this->user['org_id'] = $authUser['org_id'];
|
||||
if (isset($authUser['logged_by_authkey']) && $authUser['logged_by_authkey']) {
|
||||
$this->user['request_type'] = self::REQUEST_TYPE_API;
|
||||
}
|
||||
if (isset($authUser['authkey_id'])) {
|
||||
$this->user['authkey_id'] = $authUser['authkey_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function insert(array $data)
|
||||
{
|
||||
$logEntity = $this->newEntity($data);
|
||||
if ($logEntity->getErrors()) {
|
||||
throw new Exception($logEntity->getErrors());
|
||||
} else {
|
||||
$this->save($logEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $org
|
||||
* @return array
|
||||
*/
|
||||
public function returnDates($org = 'all')
|
||||
{
|
||||
$conditions = [];
|
||||
if ($org !== 'all') {
|
||||
$org = $this->Organisation->fetchOrg($org);
|
||||
if (empty($org)) {
|
||||
throw new NotFoundException('Invalid organisation.');
|
||||
}
|
||||
$conditions['org_id'] = $org['id'];
|
||||
}
|
||||
|
||||
$dataSource = ConnectionManager::getDataSource('default')->config['datasource'];
|
||||
if ($dataSource === 'Database/Mysql' || $dataSource === 'Database/MysqlObserver') {
|
||||
$validDates = $this->find('all', [
|
||||
'recursive' => -1,
|
||||
'fields' => ['DISTINCT UNIX_TIMESTAMP(DATE(created)) AS Date', 'count(id) AS count'],
|
||||
'conditions' => $conditions,
|
||||
'group' => ['Date'],
|
||||
'order' => ['Date'],
|
||||
]);
|
||||
} elseif ($dataSource === 'Database/Postgres') {
|
||||
if (!empty($conditions['org_id'])) {
|
||||
$condOrg = sprintf('WHERE org_id = %s', intval($conditions['org_id']));
|
||||
} else {
|
||||
$condOrg = '';
|
||||
}
|
||||
$sql = 'SELECT DISTINCT EXTRACT(EPOCH FROM CAST(created AS DATE)) AS "Date", COUNT(id) AS count
|
||||
FROM audit_logs
|
||||
' . $condOrg . '
|
||||
GROUP BY "Date" ORDER BY "Date"';
|
||||
$validDates = $this->query($sql);
|
||||
}
|
||||
$data = [];
|
||||
foreach ($validDates as $date) {
|
||||
$data[(int)$date[0]['Date']] = (int)$date[0]['count'];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -19,10 +19,11 @@ class AuthKeysTable extends AppTable
|
|||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->belongsTo(
|
||||
'Users'
|
||||
);
|
||||
$this->setDisplayField('authkey');
|
||||
$this->setDisplayField('comment');
|
||||
}
|
||||
|
||||
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
|
||||
|
|
|
@ -19,6 +19,7 @@ class BroodsTable extends AppTable
|
|||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('Timestamp');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->BelongsTo(
|
||||
'Organisations'
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ class EncryptionKeysTable extends AppTable
|
|||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->addBehavior('Timestamp');
|
||||
$this->belongsTo(
|
||||
'Individuals',
|
||||
|
|
|
@ -19,7 +19,7 @@ class InboxTable extends AppTable
|
|||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('Timestamp');
|
||||
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->belongsTo('Users');
|
||||
$this->setDisplayField('title');
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ class IndividualsTable extends AppTable
|
|||
$this->addBehavior('Timestamp');
|
||||
$this->addBehavior('Tags.Tag');
|
||||
$this->addBehavior('MetaFields');
|
||||
$this->addBehavior('AuditLog');
|
||||
|
||||
$this->hasMany(
|
||||
'Alignments',
|
||||
|
|
|
@ -18,6 +18,8 @@ class InstanceTable extends AppTable
|
|||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->setDisplayField('name');
|
||||
}
|
||||
|
||||
public function validationDefault(Validator $validator): Validator
|
||||
|
@ -38,6 +40,18 @@ class InstanceTable extends AppTable
|
|||
public function searchAll($value, $limit=5, $model=null)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
// search in metafields. FIXME: To be replaced by the meta-template system
|
||||
$metaFieldTable = TableRegistry::get('MetaFields');
|
||||
$query = $metaFieldTable->find()->where([
|
||||
'value LIKE' => '%' . $value . '%'
|
||||
]);
|
||||
$results['MetaFields']['amount'] = $query->count();
|
||||
$result = $query->limit($limit)->all()->toList();
|
||||
if (!empty($result)) {
|
||||
$results['MetaFields']['entries'] = $result;
|
||||
}
|
||||
|
||||
$models = $this->seachAllTables;
|
||||
if (!is_null($model)) {
|
||||
if (in_array($model, $this->seachAllTables)) {
|
||||
|
|
|
@ -30,6 +30,7 @@ class LocalToolsTable extends AppTable
|
|||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->addBehavior('Timestamp');
|
||||
}
|
||||
|
||||
|
@ -142,7 +143,8 @@ class LocalToolsTable extends AppTable
|
|||
'connector' => $connector_type,
|
||||
'connector_version' => $connector_class->version,
|
||||
'connector_description' => $connector_class->description,
|
||||
'connector_settings' => $connector_class->settings ?? []
|
||||
'connector_settings' => $connector_class->settings ?? [],
|
||||
'connector_settings_placeholder' => $connector_class->settingsPlaceholder ?? [],
|
||||
];
|
||||
if ($includeConnections) {
|
||||
$connector['connections'] = $this->healthCheck($connector_type, $connector_class);
|
||||
|
@ -287,6 +289,7 @@ class LocalToolsTable extends AppTable
|
|||
return $jsonReply;
|
||||
}
|
||||
|
||||
/*
|
||||
public function findConnectable($local_tool): array
|
||||
{
|
||||
$connectors = $this->getInterconnectors($local_tool['connector']);
|
||||
|
@ -296,8 +299,8 @@ class LocalToolsTable extends AppTable
|
|||
$validTargets[$connector['connects'][1]] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
public function fetchConnection($id): object
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@ class MetaFieldsTable extends AppTable
|
|||
'MetaTemplateFields' => ['counter']
|
||||
]);
|
||||
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->belongsTo('MetaTemplates');
|
||||
$this->belongsTo('MetaTemplateFields');
|
||||
|
||||
|
|
|
@ -16,8 +16,10 @@ class OrganisationsTable extends AppTable
|
|||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('Timestamp');
|
||||
$this->addBehavior('Tags.Tag');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->hasMany(
|
||||
'Alignments',
|
||||
[
|
||||
|
|
|
@ -28,6 +28,7 @@ class OutboxProcessorsTable extends AppTable
|
|||
if (empty($this->outboxProcessors)) {
|
||||
$this->loadProcessors();
|
||||
}
|
||||
$this->addBehavior('AuditLog');
|
||||
}
|
||||
|
||||
public function getProcessor($scope, $action=null)
|
||||
|
|
|
@ -19,8 +19,8 @@ class OutboxTable extends AppTable
|
|||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('Timestamp');
|
||||
|
||||
$this->belongsTo('Users');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->setDisplayField('title');
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ class RemoteToolConnectionsTable extends AppTable
|
|||
'LocalTools'
|
||||
);
|
||||
$this->setDisplayField('id');
|
||||
$this->addBehavior('AuditLog');
|
||||
}
|
||||
|
||||
public function validationDefault(Validator $validator): Validator
|
||||
|
|
|
@ -12,6 +12,7 @@ class RolesTable extends AppTable
|
|||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->hasMany(
|
||||
'Users',
|
||||
[
|
||||
|
|
|
@ -211,6 +211,13 @@ class CerebrateSettingsProvider extends BaseSettingsProvider
|
|||
},
|
||||
'dependsOn' => 'keycloak.enabled'
|
||||
],
|
||||
'keycloak.screw' => [
|
||||
'name' => 'Screw',
|
||||
'type' => 'string',
|
||||
'severity' => 'info',
|
||||
'default' => 0,
|
||||
'description' => __('The misalignment allowed when validating JWT tokens between cerebrate and keycloak. Whilst crisp timings are essential for any timing push, perfect timing is only achievable by GSL participants. (given in seconds)')
|
||||
],
|
||||
'keycloak.mapping.org_uuid' => [
|
||||
'name' => 'org_uuid mapping',
|
||||
'type' => 'string',
|
||||
|
|
|
@ -26,6 +26,7 @@ class SettingsTable extends AppTable
|
|||
parent::initialize($config);
|
||||
$this->setTable(false);
|
||||
$this->SettingsProvider = new CerebrateSettingsProvider();
|
||||
$this->addBehavior('AuditLog');
|
||||
}
|
||||
|
||||
public function getSettings($full=false): array
|
||||
|
|
|
@ -15,6 +15,7 @@ class SharingGroupsTable extends AppTable
|
|||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('Timestamp');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->belongsTo(
|
||||
'Users'
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ use App\Settings\SettingsProvider\UserSettingsProvider;
|
|||
|
||||
class UserSettingsTable extends AppTable
|
||||
{
|
||||
protected $BOOKMARK_SETTING_NAME = 'ui.bookmarks';
|
||||
public $BOOKMARK_SETTING_NAME = 'ui.bookmarks';
|
||||
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@ use \Cake\Http\Session;
|
|||
use Cake\Http\Client;
|
||||
use Cake\Utility\Security;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Utility\Text;
|
||||
|
||||
class UsersTable extends AppTable
|
||||
{
|
||||
|
@ -20,6 +21,7 @@ class UsersTable extends AppTable
|
|||
parent::initialize($config);
|
||||
$this->addBehavior('Timestamp');
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('AuditLog');
|
||||
$this->initAuthBehaviors();
|
||||
$this->belongsTo(
|
||||
'Individuals',
|
||||
|
@ -35,6 +37,13 @@ class UsersTable extends AppTable
|
|||
'cascadeCallbacks' => false
|
||||
]
|
||||
);
|
||||
$this->belongsTo(
|
||||
'Organisations',
|
||||
[
|
||||
'dependent' => false,
|
||||
'cascadeCallbacks' => false
|
||||
]
|
||||
);
|
||||
$this->hasMany(
|
||||
'UserSettings',
|
||||
[
|
||||
|
@ -88,15 +97,35 @@ class UsersTable extends AppTable
|
|||
return $rules;
|
||||
}
|
||||
|
||||
public function test()
|
||||
{
|
||||
$this->Roles = TableRegistry::get('Roles');
|
||||
$role = $this->Roles->newEntity([
|
||||
'name' => 'admin',
|
||||
'perm_admin' => 1,
|
||||
'perm_org_admin' => 1,
|
||||
'perm_sync' => 1
|
||||
]);
|
||||
$this->Roles->save($role);
|
||||
}
|
||||
|
||||
public function checkForNewInstance(): bool
|
||||
{
|
||||
if (empty($this->find()->first())) {
|
||||
$this->Roles = TableRegistry::get('Roles');
|
||||
$role = $this->Roles->newEntity([
|
||||
'name' => 'admin',
|
||||
'perm_admin' => 1
|
||||
'perm_admin' => 1,
|
||||
'perm_org_admin' => 1,
|
||||
'perm_sync' => 1
|
||||
]);
|
||||
$this->Roles->save($role);
|
||||
$this->Organisations = TableRegistry::get('Organisations');
|
||||
$organisation = $this->Organisations->newEntity([
|
||||
'name' => 'default_organisation',
|
||||
'uuid' => Text::uuid()
|
||||
]);
|
||||
$this->Organisations->save($organisation);
|
||||
$this->Individuals = TableRegistry::get('Individuals');
|
||||
$individual = $this->Individuals->newEntity([
|
||||
'email' => 'admin@admin.test',
|
||||
|
@ -108,6 +137,7 @@ class UsersTable extends AppTable
|
|||
'username' => 'admin',
|
||||
'password' => 'Password1234',
|
||||
'individual_id' => $individual->id,
|
||||
'organisation_id' => $organisation->id,
|
||||
'role_id' => $role->id
|
||||
]);
|
||||
$this->save($user);
|
||||
|
|
|
@ -44,5 +44,6 @@ class AppView extends View
|
|||
$this->loadHelper('FormFieldMassage');
|
||||
$this->loadHelper('Paginator', ['templates' => 'cerebrate-pagination-templates']);
|
||||
$this->loadHelper('Tags.Tag');
|
||||
$this->loadHelper('ACL');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace App\View\Helper;
|
||||
|
||||
use Cake\View\Helper;
|
||||
|
||||
// This helper helps determining the brightness of a colour (initially only used for the tagging) in order to decide
|
||||
// what text colour to use against the background (black or white)
|
||||
class ACLHelper extends Helper {
|
||||
|
||||
private $roleAccess = [];
|
||||
|
||||
public function checkAccess($controller, $action) {
|
||||
if (empty($this->roleAccess)) {
|
||||
$this->roleAccess = $this->getView()->get('roleAccess');
|
||||
}
|
||||
if (
|
||||
in_array($action, $this->roleAccess['*']) ||
|
||||
(isset($this->roleAccess[$controller]) && in_array($action, $this->roleAccess[$controller]))
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -651,13 +651,13 @@ class BoostrapTable extends BootstrapGeneric
|
|||
],
|
||||
]);
|
||||
foreach ($this->items as $i => $row) {
|
||||
$body .= $this->genRow($row);
|
||||
$body .= $this->genRow($row, $i);
|
||||
}
|
||||
$body .= $this->closeNode('tbody');
|
||||
return $body;
|
||||
}
|
||||
|
||||
private function genRow($row)
|
||||
private function genRow($row, $rowIndex)
|
||||
{
|
||||
$html = $this->openNode('tr', [
|
||||
'class' => [
|
||||
|
@ -672,21 +672,21 @@ class BoostrapTable extends BootstrapGeneric
|
|||
$key = $field;
|
||||
}
|
||||
$cellValue = Hash::get($row, $key);
|
||||
$html .= $this->genCell($cellValue, $field, $row, $i);
|
||||
$html .= $this->genCell($cellValue, $field, $row, $rowIndex);
|
||||
}
|
||||
} else { // indexed array
|
||||
foreach ($row as $cellValue) {
|
||||
$html .= $this->genCell($cellValue, $field, $row, $i);
|
||||
foreach ($row as $i => $cellValue) {
|
||||
$html .= $this->genCell($cellValue, 'index', $row, $rowIndex);
|
||||
}
|
||||
}
|
||||
$html .= $this->closeNode('tr');
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function genCell($value, $field = [], $row = [], $i = 0)
|
||||
private function genCell($value, $field=[], $row=[], $rowIndex=0)
|
||||
{
|
||||
if (isset($field['formatter'])) {
|
||||
$cellContent = $field['formatter']($value, $row, $i);
|
||||
$cellContent = $field['formatter']($value, $row, $rowIndex);
|
||||
} else if (isset($field['element'])) {
|
||||
$cellContent = $this->btHelper->getView()->element($field['element'], [
|
||||
'data' => [$value],
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<redoc spec-url='<?php echo $url ?>'></redoc>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/IndexTable/index_table', [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
'top_bar' => [
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'value'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'name' => '#',
|
||||
'sort' => 'id',
|
||||
'data_path' => 'id',
|
||||
],
|
||||
[
|
||||
'name' => __('IP'),
|
||||
'sort' => 'request_ip',
|
||||
'data_path' => 'request_ip',
|
||||
],
|
||||
[
|
||||
'name' => __('Username'),
|
||||
'sort' => 'user.username',
|
||||
'data_path' => 'user.username',
|
||||
],
|
||||
[
|
||||
'name' => __('Title'),
|
||||
'data_path' => 'title',
|
||||
],
|
||||
[
|
||||
'name' => __('Model'),
|
||||
'sort' => 'model',
|
||||
'data_path' => 'model',
|
||||
],
|
||||
[
|
||||
'name' => __('Model ID'),
|
||||
'sort' => 'model',
|
||||
'data_path' => 'model_id',
|
||||
],
|
||||
[
|
||||
'name' => __('Action'),
|
||||
'sort' => 'request_action',
|
||||
'data_path' => 'request_action',
|
||||
],
|
||||
[
|
||||
'name' => __('Changed'),
|
||||
'sort' => 'changed',
|
||||
'data_path' => 'changed',
|
||||
'element' => 'json'
|
||||
],
|
||||
],
|
||||
'title' => __('Logs'),
|
||||
'description' => null,
|
||||
'pull' => 'right',
|
||||
'actions' => []
|
||||
]
|
||||
]);
|
||||
echo '</div>';
|
||||
?>
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
echo $this->element(
|
||||
'/genericElements/SingleViews/single_view',
|
||||
[
|
||||
'data' => $entity,
|
||||
'fields' => [
|
||||
[
|
||||
'key' => __('ID'),
|
||||
'path' => 'id'
|
||||
],
|
||||
[
|
||||
'key' => __('Type'),
|
||||
'path' => 'type'
|
||||
],
|
||||
[
|
||||
'key' => __('Owner'),
|
||||
'path' => 'owner_id',
|
||||
'owner_model_path' => 'owner_model',
|
||||
'type' => 'owner'
|
||||
],
|
||||
[
|
||||
'key' => __('Revoked'),
|
||||
'path' => 'revoked'
|
||||
],
|
||||
|
||||
[
|
||||
'key' => __('Key'),
|
||||
'path' => 'encryption_key'
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
|
@ -23,7 +23,8 @@
|
|||
),
|
||||
array(
|
||||
'field' => 'tag_list',
|
||||
'type' => 'tags'
|
||||
'type' => 'tags',
|
||||
'requirements' => $this->request->getParam('action') === 'edit'
|
||||
),
|
||||
),
|
||||
'submit' => array(
|
||||
|
|
|
@ -19,14 +19,25 @@
|
|||
</span>', h($tableName), $tableResult['amount']);
|
||||
|
||||
foreach ($tableResult['entries'] as $entry) {
|
||||
$section .= sprintf('<a class="dropdown-item" href="%s">%s</a>',
|
||||
Cake\Routing\Router::URL([
|
||||
'controller' => Cake\Utility\Inflector::pluralize($entry->getSource()),
|
||||
'action' => 'view',
|
||||
h($entry['id'])
|
||||
]),
|
||||
h($entry[$fieldPath])
|
||||
);
|
||||
if ($entry->getSource() == 'MetaFields') {
|
||||
$section .= sprintf('<a class="dropdown-item" href="%s">%s</a>',
|
||||
Cake\Routing\Router::URL([
|
||||
'controller' => Cake\Utility\Inflector::pluralize($entry->scope),
|
||||
'action' => 'view',
|
||||
h($entry->parent_id)
|
||||
]),
|
||||
sprintf('%s (%s::%s)', h($entry->value), h($entry->scope), h($entry->field))
|
||||
);
|
||||
} else {
|
||||
$section .= sprintf('<a class="dropdown-item" href="%s">%s</a>',
|
||||
Cake\Routing\Router::URL([
|
||||
'controller' => Cake\Utility\Inflector::pluralize($entry->getSource()),
|
||||
'action' => 'view',
|
||||
h($entry['id'])
|
||||
]),
|
||||
h($entry[$fieldPath])
|
||||
);
|
||||
}
|
||||
}
|
||||
$remaining = $tableResult['amount'] - count($tableResult['entries']);
|
||||
if ($remaining > 0) {
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
'codemirror' => [
|
||||
'height' => '10rem',
|
||||
'hints' => $connectors[0]['connector_settings']
|
||||
]
|
||||
],
|
||||
'placeholder' => json_encode($connectors[0]['connector_settings_placeholder'], JSON_FORCE_OBJECT | JSON_PRETTY_PRINT)
|
||||
],
|
||||
[
|
||||
'field' => 'description',
|
||||
|
|
|
@ -89,12 +89,14 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
'url_params_data_paths' => ['id'],
|
||||
'icon' => 'eye'
|
||||
],
|
||||
/*
|
||||
[
|
||||
'open_modal' => '/localTools/connectLocal/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connectorName)),
|
||||
'icon' => 'plug'
|
||||
],
|
||||
*/
|
||||
[
|
||||
'open_modal' => '/localTools/edit/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
|
|
|
@ -7,17 +7,13 @@
|
|||
array(
|
||||
'field' => 'name'
|
||||
),
|
||||
array(
|
||||
'field' => 'description',
|
||||
'type' => 'textarea'
|
||||
),
|
||||
array(
|
||||
'field' => 'uuid',
|
||||
'label' => 'UUID',
|
||||
'type' => 'uuid'
|
||||
),
|
||||
array(
|
||||
'field' => 'URL'
|
||||
'field' => 'url'
|
||||
),
|
||||
array(
|
||||
'field' => 'nationality'
|
||||
|
|
|
@ -97,12 +97,14 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
[
|
||||
'open_modal' => '/organisations/edit/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'edit'
|
||||
'icon' => 'edit',
|
||||
'requirement' => $loggedUser['role']['perm_admin']
|
||||
],
|
||||
[
|
||||
'open_modal' => '/organisations/delete/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'trash'
|
||||
'icon' => 'trash',
|
||||
'requirement' => $loggedUser['role']['perm_admin']
|
||||
],
|
||||
]
|
||||
]
|
||||
|
|
|
@ -78,12 +78,14 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
[
|
||||
'open_modal' => '/roles/edit/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'edit'
|
||||
'icon' => 'edit',
|
||||
'requirement' => !empty($loggedUser['role']['perm_admin'])
|
||||
],
|
||||
[
|
||||
'open_modal' => '/roles/delete/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'trash'
|
||||
'icon' => 'trash',
|
||||
'requirement' => !empty($loggedUser['role']['perm_admin'])
|
||||
],
|
||||
]
|
||||
]
|
||||
|
|
|
@ -10,7 +10,8 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
'data' => [
|
||||
'type' => 'simple',
|
||||
'text' => __('Add sharing group'),
|
||||
'popover_url' => '/SharingGroups/add'
|
||||
'popover_url' => '/SharingGroups/add',
|
||||
'requirement' => $this->ACL->checkAccess('SharingGroups', 'add')
|
||||
]
|
||||
]
|
||||
],
|
||||
|
@ -35,6 +36,11 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
'class' => 'short',
|
||||
'data_path' => 'name',
|
||||
],
|
||||
[
|
||||
'name' => __('Owner'),
|
||||
'class' => 'short',
|
||||
'data_path' => 'organisation.name'
|
||||
],
|
||||
[
|
||||
'name' => __('UUID'),
|
||||
'sort' => 'uuid',
|
||||
|
@ -43,9 +49,9 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
],
|
||||
[
|
||||
'name' => __('Members'),
|
||||
'data_path' => 'alignments',
|
||||
'data_path' => 'sharing_group_orgs',
|
||||
'element' => 'count_summary',
|
||||
'url' => '/sharingGroups/view/{{id}}',
|
||||
'url' => '/sharingGroups/view/{{url_data}}',
|
||||
'url_data_path' => 'id'
|
||||
]
|
||||
],
|
||||
|
|
|
@ -14,12 +14,19 @@
|
|||
'field' => 'username',
|
||||
'autocomplete' => 'off'
|
||||
],
|
||||
[
|
||||
'field' => 'organisation_id',
|
||||
'type' => 'dropdown',
|
||||
'label' => __('Associated organisation'),
|
||||
'options' => $dropdownData['organisation']
|
||||
],
|
||||
[
|
||||
'field' => 'password',
|
||||
'label' => __('Password'),
|
||||
'type' => 'password',
|
||||
'required' => $this->request->getParam('action') === 'add' ? 'required' : false,
|
||||
'autocomplete' => 'new-password'
|
||||
'autocomplete' => 'new-password',
|
||||
'value' => ''
|
||||
],
|
||||
[
|
||||
'field' => 'confirm_password',
|
||||
|
|
|
@ -51,6 +51,13 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
'sort' => 'username',
|
||||
'data_path' => 'username',
|
||||
],
|
||||
[
|
||||
'name' => __('Organisation'),
|
||||
'sort' => 'organisation.name',
|
||||
'data_path' => 'organisation.name',
|
||||
'url' => '/organisations/view/{{0}}',
|
||||
'url_vars' => ['organisation.id']
|
||||
],
|
||||
[
|
||||
'name' => __('Email'),
|
||||
'sort' => 'individual.email',
|
||||
|
|
|
@ -24,7 +24,7 @@ use Cake\Core\Configure;
|
|||
echo $this->Form->control('password', ['type' => 'password', 'label' => 'Password', 'class' => 'form-control mb-3', 'placeholder' => __('Password')]);
|
||||
echo $this->Form->control(__('Login'), ['type' => 'submit', 'class' => 'btn btn-primary']);
|
||||
echo $this->Form->end();
|
||||
if (!empty(Configure::read('Cerebrate')['security.registration.self-registration'])) {
|
||||
if (!empty(Configure::read('security.registration.self-registration'))) {
|
||||
echo '<div class="text-end">';
|
||||
echo sprintf('<span class="text-secondary ms-auto" style="font-size: 0.8rem">%s <a href="/users/register" class="text-decoration-none link-primary fw-bold">%s</a></span>', __('Doesn\'t have an account?'), __('Sign up'));
|
||||
echo '</div>';
|
||||
|
|
|
@ -21,6 +21,14 @@ echo $this->element(
|
|||
'path' => 'individual.email'
|
||||
],
|
||||
[
|
||||
'type' => 'generic',
|
||||
'key' => __('Organisation'),
|
||||
'path' => 'organisation.name',
|
||||
'url' => '/organisations/view/{{0}}',
|
||||
'url_vars' => 'organisation.id'
|
||||
],
|
||||
[
|
||||
'type' => 'generic',
|
||||
'key' => __('Role'),
|
||||
'path' => 'role.name',
|
||||
'url' => '/roles/view/{{0}}',
|
||||
|
@ -48,8 +56,8 @@ echo $this->element(
|
|||
'title' => __('Authentication keys')
|
||||
],
|
||||
[
|
||||
'url' => '/EncryptionKeys/index?Users.id={{0}}',
|
||||
'url_params' => ['id'],
|
||||
'url' => '/EncryptionKeys/index?owner_id={{0}}',
|
||||
'url_params' => ['individual_id'],
|
||||
'title' => __('Encryption keys')
|
||||
],
|
||||
[
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
$controlParams = [
|
||||
'options' => $fieldData['options'],
|
||||
'empty' => $fieldData['empty'] ?? false,
|
||||
'value' => $fieldData['value'] ?? [],
|
||||
'value' => $fieldData['value'] ?? null,
|
||||
'multiple' => $fieldData['multiple'] ?? false,
|
||||
'disabled' => $fieldData['disabled'] ?? false,
|
||||
'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select'
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
$params['div'] = false;
|
||||
$params['class'] .= ' form-control';
|
||||
$params['value'] = '';
|
||||
echo $this->FormFieldMassage->prepareFormElement($this->Form, $params, $fieldData);
|
||||
?>
|
|
@ -1,9 +1,12 @@
|
|||
<?php
|
||||
$data = h($this->Hash->extract($row, $field['data_path']));
|
||||
$data = $this->Hash->extract($row, $field['data_path']);
|
||||
// I feed dirty for this...
|
||||
if (is_array($data) && count($data) === 1 && isset($data[0])) {
|
||||
$data = $data[0];
|
||||
}
|
||||
if (!is_array($data)) {
|
||||
$data = json_decode($data, true);
|
||||
}
|
||||
echo sprintf(
|
||||
'<div class="json_container_%s"></div>',
|
||||
h($k)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue