├── .gitignore ├── limited-guest-access ├── icon.png ├── logo.png ├── www.conf ├── tls.conf ├── run.sh ├── config.json ├── default.conf ├── Dockerfile ├── README.md ├── CHANGELOG.md └── app │ ├── user │ ├── index.php │ └── actions.php │ └── admin │ ├── actions.php │ └── index.php └── repository.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | */.idea 3 | -------------------------------------------------------------------------------- /limited-guest-access/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TekniskSupport/homeassistant-addons/HEAD/limited-guest-access/icon.png -------------------------------------------------------------------------------- /limited-guest-access/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TekniskSupport/homeassistant-addons/HEAD/limited-guest-access/logo.png -------------------------------------------------------------------------------- /repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Home Assistant add-on repository by Iesus", 3 | "url": "https://github.com/TekniskSupport/homeassistant-addons", 4 | "maintainer": "Iesus" 5 | } 6 | -------------------------------------------------------------------------------- /limited-guest-access/www.conf: -------------------------------------------------------------------------------- 1 | [www] 2 | user = nobody 3 | group = nobody 4 | listen = 127.0.0.1:9000 5 | pm = dynamic 6 | pm.max_children = 5 7 | pm.start_servers = 2 8 | pm.min_spare_servers = 1 9 | pm.max_spare_servers = 3 10 | clear_env = no 11 | -------------------------------------------------------------------------------- /limited-guest-access/tls.conf: -------------------------------------------------------------------------------- 1 | listen 8888 http2 ssl; 2 | 3 | ssl_certificate /ssl/fullchain.pem; 4 | ssl_certificate_key /ssl/privkey.pem; 5 | add_header Strict-Transport-Security "max-age=31536000; includeSubdomains"; 6 | ssl_protocols TLSv1.2; 7 | ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; 8 | ssl_prefer_server_ciphers on; 9 | ssl_ecdh_curve secp384r1; -------------------------------------------------------------------------------- /limited-guest-access/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bashio 2 | chmod 777 /data/ 3 | mkdir -p /data/links 4 | chmod -R 666 /data/links 5 | chmod 777 /data/links 6 | chmod 644 /data/options.json 7 | is_ssl_active=$(cat /data/options.json |jq .activate_tls) 8 | if [ $is_ssl_active = true ]; then 9 | if test -f "/ssl/privkey.pem"; then 10 | sed -i 's/ listen 8888 default_server;/ include \/etc\/nginx\/snippets\/tls.conf;/g' /etc/nginx/http.d/default.conf 11 | fi; 12 | fi; 13 | 14 | if ! pgrep "nginx" > /dev/null; then 15 | nginx && php-fpm83 16 | fi 17 | 18 | while true; do sleep 1000; done 19 | 20 | exec "$@" 21 | -------------------------------------------------------------------------------- /limited-guest-access/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Limited guest access", 3 | "version": "0.1.33", 4 | "slug": "limited_guest_access", 5 | "description": "Allow guests to access limited functions", 6 | "arch": ["armhf", "armv7", "aarch64", "amd64", "i386"], 7 | "boot": "auto", 8 | "init": false, 9 | "options": { 10 | "activate_tls": false, 11 | "external_url": "https://your-external-visitor-url.tld:port/" 12 | }, 13 | "schema": { 14 | "activate_tls": "bool", 15 | "external_url": "str" 16 | }, 17 | "ports": { 18 | "8888/tcp": 8888, 19 | "8899/tcp": 8899 20 | }, 21 | "ingress": true, 22 | "homeassistant_api": true, 23 | "map": [ 24 | "ssl", 25 | "share" 26 | ], 27 | "panel_icon": "mdi:card-account-details-outline" 28 | } 29 | -------------------------------------------------------------------------------- /limited-guest-access/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8888 default_server; 3 | root /var/www/user; 4 | 5 | index index.php index.html index.htm index.nginx-debian.html; 6 | server_name _; 7 | 8 | rewrite ^/(.*)/$ /index.php?link=$1 last; 9 | location ~ \.php$ { 10 | fastcgi_pass 127.0.0.1:9000; 11 | fastcgi_index index.php; 12 | include /etc/nginx/fastcgi.conf; 13 | } 14 | } 15 | 16 | server { 17 | listen 8099; 18 | listen 8899; 19 | root /var/www/admin; 20 | 21 | index index.php index.html index.htm index.nginx-debian.html; 22 | server_name _; 23 | location / { 24 | try_files $uri $uri/ =404; 25 | } 26 | location ~ \.php$ { 27 | fastcgi_pass 127.0.0.1:9000; 28 | fastcgi_index index.php; 29 | fastcgi_buffer_size 4K; 30 | fastcgi_buffers 64 4k; 31 | include /etc/nginx/fastcgi.conf; 32 | } 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /limited-guest-access/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BUILD_FROM 2 | FROM $BUILD_FROM 3 | 4 | ENV LANG C.UTF-8 5 | 6 | # PHP_INI_DIR to be symmetrical with official php docker image 7 | ENV PHP_INI_DIR /etc/php/8.3 8 | 9 | # Persistent runtime dependencies 10 | 11 | RUN apk add --no-cache nginx openrc 12 | 13 | RUN apk --update add \ 14 | php \ 15 | php-bcmath \ 16 | php-dom \ 17 | php-ctype \ 18 | php-curl \ 19 | php-fileinfo \ 20 | php-fpm \ 21 | php-gd \ 22 | php-iconv \ 23 | php-intl \ 24 | php-json \ 25 | php-mbstring \ 26 | php-mysqlnd \ 27 | php-opcache \ 28 | php-openssl \ 29 | php-pdo \ 30 | php-pdo_mysql \ 31 | php-pdo_pgsql \ 32 | php-pdo_sqlite \ 33 | php-phar \ 34 | php-posix \ 35 | php-simplexml \ 36 | php-session \ 37 | php-soap \ 38 | php-tokenizer \ 39 | php-xml \ 40 | php-xmlreader \ 41 | php-xmlwriter \ 42 | php-zip \ 43 | curl 44 | 45 | EXPOSE 8888 46 | EXPOSE 8899 47 | 48 | COPY run.sh / 49 | COPY tls.conf /etc/nginx/snippets/ 50 | COPY default.conf /etc/nginx/http.d/ 51 | COPY www.conf /etc/php83/php-fpm.d/ 52 | COPY app/admin/ /var/www/admin/ 53 | COPY app/user/ /var/www/user/ 54 | RUN chmod a+x /run.sh 55 | RUN mkdir -p /run/nginx 56 | RUN mkdir -p /share/limited-guest-access 57 | RUN ln -sf /dev/stderr /var/log/nginx/error.log 58 | CMD ["/run.sh"] 59 | -------------------------------------------------------------------------------- /limited-guest-access/README.md: -------------------------------------------------------------------------------- 1 | ### What is this? 2 | 3 | This add-on gives you the opportunity to give a guest user access 4 | to certain home assistant devices. It works just like the view "services" 5 | in developer tools in home assistant. 6 | 7 | When a link is created and service calls added the guest user gets a button 8 | in the user view (visit the link to view) 9 | 10 | You can set a time frame to the actions, as well as make an action "one time use" 11 | 12 | ### How to use 13 | 14 | First open the admin-interface (default port 8899), 15 | click the "create new link" button 16 | 17 | Select which service you want to make public and 18 | enter the data that is required by the service (for example entity_id) 19 | 20 | Within the valid time frame chosen when you created the action, 21 | your guest can access the external_link/name_of_link 22 | (for example http://your-external-url.tld:8888/adf12345) 23 | to be able to trigger the actions using a button. 24 | 25 | ### Inject code into user view 26 | 27 | There are four custom entry points into the user view, that you 28 | can use these to modify the design of the end user page 29 | - style.css 30 | - script.js 31 | - header.htm 32 | - footer.htm 33 | 34 | The first two are injected into the `` section of the page, just above `` 35 | The two latter are injected into the `` section, the first of which just after the opening tag, 36 | and the footer just before closing the body. 37 | 38 | To make use of this, you should create the files in the 39 | /share/limited-guest-access/ directory and adjust them to fit your needs. 40 | 41 | ### Install instructions 42 | 43 | In home assistant, head to Supervisor -> add-on store 44 | and press the [...] menu, then click repositories and paste 45 | `https://github.com/TekniskSupport/homeassistant-addons` 46 | 47 | The add-on will now show up as a card along with the other add-ons 48 | 49 | Hit install, then edit configuration value and hit start. 50 | 51 | If you are unable to start the add-on make sure nothing else is running 52 | on the selected ports. -------------------------------------------------------------------------------- /limited-guest-access/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.33] - 2024-03-28 2 | ### bugfix 3 | - Fixed security issue, where a user could get access to a link not authenticated to, if authenticated to another link 4 | 5 | ## [0.1.32] - 2023-12-14 6 | ### bugfix 7 | - upgraded to php8.2 8 | 9 | ## [0.1.31] - 2023-05-24 10 | ### bugfix 11 | - fixed typo in config folder 12 | 13 | ## [0.1.30] - 2023-05-24 14 | ### bugfix 15 | - New php 8.1 config folder 16 | 17 | ## [0.1.29] - 2023-05-24 18 | ### bugfix 19 | - New php binary name 20 | 21 | ## [0.1.28] - 2022-12-13 22 | ### bugfix 23 | - Fixes "array key not found" 24 | 25 | ## [0.1.27] - 2022-09-16 26 | ### bugfix 27 | - Changing link no longer creates duplicate 28 | 29 | ## [0.1.26] - 2022-09-16 30 | ### feature 31 | - Added usage log 32 | - fixed bug in user view 33 | 34 | ## [0.1.25] - 2022-09-16 35 | ### feature 36 | - Added friendly name to list of entities 37 | - methods and variables now have return types declared 38 | 39 | ## [0.1.24] - 2022-08-25 40 | ### bugfix 41 | - set timezone in admin ui 42 | - filter invalid charcters from custom URL 43 | 44 | ## [0.1.23] - 2021-06-23 45 | ### bugfix 46 | - Updated to php8 47 | 48 | ## [0.1.21] - 2021-05-26 49 | ### bugfix 50 | - Init:false 51 | 52 | ## [0.1.18] - 2020-10-28 53 | ### bugfix 54 | - Fixed permissions to options.json 55 | 56 | ## [0.1.17] - 2020-09-16 57 | ### bugfix 58 | - inject directory not an array 59 | 60 | ## [0.1.16] - 2020-09-16 61 | ### re-added 62 | - data directory for style files 63 | 64 | ## [0.1.15] - 2020-09-15 65 | ### Added 66 | - device type class to user links 67 | 68 | ## [0.1.14] - 2020-09-12 69 | ### Added 70 | - Moved code injection to /share/ 71 | 72 | ## [0.1.13] - 2020-09-11 73 | ### Added 74 | - Display state of switches and more 75 | 76 | ## [0.1.12] - 2020-09-11 77 | ### Added 78 | - code injection points to user view 79 | 80 | ## [0.1.11] - 2020-09-11 81 | ### Fixes 82 | - Fixed typos 83 | 84 | ## [0.1.10] - 2020-09-10 85 | ### Added 86 | - Optional password to link 87 | - Custom link path 88 | - (crude) Theme support 89 | 90 | ## [0.1.9] - 2020-09-05 91 | ### Bugfix 92 | - Added default start and end time 93 | - Fixed bug with first action add did not create service call data fields 94 | 95 | ## [0.1.8] - 2020-09-05 96 | ### Feature 97 | - Added Panel icon 98 | 99 | ## [0.1.7] - 2020-09-05 100 | ### Feature 101 | - Added TLS support 102 | 103 | ## [0.1.6] - 2020-09-05 104 | ### Bugfix 105 | - High CPU usage 106 | 107 | ## [0.1.5] - 2020-09-03 108 | ### Added 109 | - Re enabled port 8899 110 | - Dropdown options are alphabetically sorted 111 | - You can now edit an action 112 | 113 | ## [0.1.4] - 2020-09-02 114 | ### Bugfix 115 | - Background color, not transparent 116 | 117 | ## [0.1.3] - 2020-09-02 118 | ### Added 119 | - Ingress mode 120 | 121 | ## [0.1.2] - 2020-09-02 122 | ### Bugfix 123 | - Removed empty elements from data object 124 | 125 | ## [0.1.1] - 2020-09-02 126 | ### Added 127 | - lgootype 128 | - icon 129 | 130 | ## [0.1.0] - 2020-09-02 131 | ### Breaking change 132 | - Breaking change, separated service call data from configuration 133 | 134 | ## [0.0.7] - 2020-08-31 135 | ## [0.0.6] - 2020-08-31 136 | ## [0.0.5] - 2020-08-31 137 | ## [0.0.4] - 2020-08-31 138 | ## [0.0.3] - 2020-08-31 139 | ## [0.0.2] - 2020-08-31 140 | ### Added 141 | - fix for safari 142 | - entity_id select box 143 | - service_call select box 144 | - view errors in supervisor log 145 | - placeholder for date format 146 | - timezone compatible 147 | 148 | ## [0.0.1] - 2020-08-30 149 | ### Added 150 | - initial code 151 | -------------------------------------------------------------------------------- /limited-guest-access/app/user/index.php: -------------------------------------------------------------------------------- 1 | theme) { 5 | case 'light-grey': 6 | $primaryColor = 'rgba(211,211,211,1);'; 7 | $secondaryColor = 'rgba(11,11,11,.5);'; 8 | break; 9 | case 'dark-blue': 10 | $primaryColor = 'rgba(11,11,11,1);'; 11 | $secondaryColor = 'rgba(11,11,221,.5);'; 12 | break; 13 | case 'light-blue': 14 | $primaryColor = 'rgba(221,221,221,1);'; 15 | $secondaryColor = 'rgba(11,11,221,.5);'; 16 | break; 17 | case 'default': 18 | default: 19 | $primaryColor = 'rgba(11,11,11,1);'; 20 | $secondaryColor = 'rgba(40,40,40,.5);'; 21 | break; 22 | } 23 | ?> 24 | 25 | 26 | Remote control 27 | 28 | 29 | 125 | inject('style.css') ; ?> 126 | inject('script.js') ; ?> 127 | 128 | 129 | inject('header.htm') ; ?> 130 | passwordProtected && !$actions->authenticated) :?> 132 |
133 |
134 | 135 | 136 |
137 |
138 | getFilteredActions(); 141 | 142 | if (isset($_GET['performedAction']) && !is_null($_GET['performedAction'])) { 143 | echo '

Performing: ' . urldecode($_GET['performedAction']) . "

"; 144 | } 145 | 146 | if (!$actions) { 147 | throw new \Exception('Could not get actions'); 148 | } 149 | 150 | foreach ($availableActions as $id => $data) : 151 | $classes = []; 152 | $state = (isset($data->service_call_data->entity_id) 153 | && !empty($data->service_call_data->entity_id)) 154 | ? json_decode($actions->getState($data->service_call_data->entity_id)) 155 | : false; 156 | $deviceType = (isset($data->service_call_data->entity_id) 157 | && !empty($data->service_call_data->entity_id)) 158 | ? explode('.', $data->service_call_data->entity_id)[0] 159 | : false; 160 | if(isset($state->state)) { 161 | $classes[] = 'state-' . $state->state; 162 | } 163 | if($deviceType) { 164 | $classes[] = 'device-type-'. $deviceType; 165 | } 166 | $classes = implode(' ', $classes); 167 | ?> 168 | friendly_name ?> 170 | 174 | inject('footer.htm') ; ?> 175 | 176 | 177 | -------------------------------------------------------------------------------- /limited-guest-access/app/admin/actions.php: -------------------------------------------------------------------------------- 1 | externalUrl = $options->external_url; 22 | 23 | $this->handleRequest(); 24 | $this->getAllLinks(); 25 | } 26 | 27 | public function getAllLinks(): bool|array 28 | { 29 | if ($this->isDirty || !isset($this->allLinks)) { 30 | $this->allLinks = glob(self::DATA_DIR . '*.json'); 31 | } 32 | 33 | return $this->allLinks; 34 | } 35 | 36 | protected function getId(): string 37 | { 38 | if (isset($_REQUEST['id']) && ctype_xdigit($_REQUEST['id'])) 39 | return $_REQUEST['id']; 40 | elseif (isset($_REQUEST['id']) 41 | && preg_match('/^([a-zA-Z0-9_-]+)$/', $_REQUEST['id'])) 42 | return $_REQUEST['id']; 43 | else 44 | throw new \Exception('No ID given!'); 45 | } 46 | 47 | protected function handleRequest(): self 48 | { 49 | if (!isset($_GET['action'])) { 50 | 51 | return $this; 52 | } 53 | 54 | switch ($_GET['action']) { 55 | case 'createNamedLink': 56 | $linkPath = !empty($_REQUEST['linkPath']) 57 | ? $_REQUEST['linkPath'] 58 | : $this->generateHash(); 59 | $password = !empty($_REQUEST['password']) 60 | ? password_hash($_REQUEST['password'], CRYPT_BLOWFISH) 61 | : null; 62 | $theme = $_REQUEST['theme']; 63 | 64 | $this->generateNewLink($theme, $linkPath, $password)->redirect(); 65 | break; 66 | case 'generateNewLink': 67 | $this->generateNewLink()->redirect(); 68 | break; 69 | case 'deleteLink': 70 | $hash = $this->getId(); 71 | $this->deleteLink($hash)->redirect(); 72 | break; 73 | case 'removeAction': 74 | $this->removeAction($this->getId(), $_GET['action_id'])->redirect(); 75 | break; 76 | case 'addActionToLink': 77 | $this->addActionToLink($this->getId())->redirect(); 78 | break; 79 | case 'editAction': 80 | $this->addActionToLink($this->getId(), $_GET['action_id'])->redirect(); 81 | break; 82 | } 83 | 84 | return $this; 85 | } 86 | 87 | protected function addActionToLink( 88 | string $hash, 89 | ?string $id = null 90 | ): self 91 | { 92 | $link = json_decode(file_get_contents(self::DATA_DIR . $hash . '.json'), true); 93 | if (!$link) { 94 | $link = []; 95 | } 96 | $id = $id ?? uniqid(); 97 | $newData[$id] = [ 98 | 'friendly_name' => $_POST['friendly_name'], 99 | 'service_call' => $_POST['service_call'], 100 | 'valid_from' => $_POST['valid_from'] ?? 0, 101 | 'expiry_time' => $_POST['expiry_time'] ?? null, 102 | 'one_time_use' => (isset($_POST['one_time_use']))? 1: 0, 103 | ]; 104 | 105 | foreach ($_POST['dynamic_field'] as $key => $additionalField) { 106 | $newData[$id]['service_call_data'][$key] = $additionalField; 107 | } 108 | 109 | if (json_encode($newData)) { 110 | $json = json_encode(array_merge($link, $newData)); 111 | } 112 | 113 | file_put_contents(self::DATA_DIR . $hash . '.json', $json); 114 | 115 | return $this; 116 | } 117 | 118 | protected function removeAction(string $hash, string $actionId): self 119 | { 120 | $json = json_decode(file_get_contents(self::DATA_DIR . $hash . '.json'),true); 121 | unset($json[$actionId]); 122 | $json = json_encode($json); 123 | file_put_contents(self::DATA_DIR . $hash . '.json', $json); 124 | 125 | return $this; 126 | } 127 | 128 | protected function generateNewLink( 129 | string $theme = 'default', 130 | ?string $linkPath = null, 131 | ?string $password = null 132 | ): self 133 | { 134 | if (!$linkPath) { 135 | $hash = $this->generateHash(); 136 | } else { 137 | $hash = $linkPath; 138 | } 139 | 140 | if (touch(self::DATA_DIR . $hash . '.json')) { 141 | $this->isDirty = true; 142 | } 143 | 144 | $linkData = ['linkData' => 145 | [ 146 | 'password' => $password, 147 | 'theme' => $theme 148 | ] 149 | ]; 150 | 151 | file_put_contents( 152 | self::DATA_DIR . $hash . '.json', 153 | json_encode($linkData) 154 | ); 155 | 156 | return $this; 157 | } 158 | 159 | protected function deleteLink(string $hash): self 160 | { 161 | if (unlink(self::DATA_DIR . $hash . '.json')) { 162 | $this->isDirty = true; 163 | } else { 164 | throw new \Exception('Unable to delete file'); 165 | } 166 | 167 | return $this; 168 | } 169 | 170 | protected function redirect(): self 171 | { 172 | header("Location: ?"); 173 | 174 | return $this; 175 | } 176 | 177 | protected function generateHash(): string 178 | { 179 | $hash = mb_substr(md5(time()), 0, 6); 180 | if (file_exists(self::DATA_DIR . $hash . '.json')) { 181 | $hash = $this->generateHash(); 182 | } 183 | 184 | return $hash; 185 | } 186 | 187 | public function getServiceData(): string|bool 188 | { 189 | $ch = curl_init(self::API_URL . 'services'); 190 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 191 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ 192 | "Authorization: Bearer {$_SERVER['SUPERVISOR_TOKEN']}" 193 | ] 194 | ); 195 | 196 | return curl_exec($ch); 197 | } 198 | 199 | public function getStates(): string|bool 200 | { 201 | $ch = curl_init(self::API_URL . 'states'); 202 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 203 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ 204 | "Authorization: Bearer {$_SERVER['SUPERVISOR_TOKEN']}" 205 | ] 206 | ); 207 | 208 | return curl_exec($ch); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /limited-guest-access/app/user/actions.php: -------------------------------------------------------------------------------- 1 | getLink() . '.json')) { 18 | http_response_code(401); 19 | throw new \Exception('Not allowed'); 20 | } else { 21 | $this->data = json_decode( 22 | file_get_contents(self::DATA_DIR . $this->getLink() . '.json') 23 | ); 24 | 25 | if (isset($this->data->linkData->theme)) { 26 | $this->theme = $this->data->linkData->theme; 27 | } 28 | 29 | $linkHash = sha1($this->getLink()); 30 | if (isset($this->data->linkData->password) && !empty($this->data->linkData->password)) { 31 | $this->passwordProtected = true; 32 | session_start(); 33 | if ($_SESSION['authenticated-'.$linkHash] ?? false === true) { 34 | $this->authenticated = true; 35 | } 36 | if (isset($_POST['password']) && password_verify($_POST['password'], $this->data->linkData->password)) { 37 | $this->authenticated = true; 38 | $_SESSION['authenticated-'.$linkHash] = true; 39 | } 40 | } 41 | } 42 | 43 | if (isset($_GET['action'])) { 44 | $availableActions = $this->getFilteredActions(); 45 | $actionData = $availableActions->{$this->getAction()}; 46 | if (!$actionData) { 47 | throw new \Exception('unknown action'); 48 | } 49 | 50 | $this 51 | ->performAction($actionData) 52 | ->addLog($this->getAction()) 53 | ->invalidateAction($actionData, $this->getAction()) 54 | ->redirect('?performedAction='. urlencode($actionData->friendly_name)); 55 | } 56 | } 57 | 58 | public function getAllActions(): object 59 | { 60 | 61 | return $this->data; 62 | } 63 | 64 | public function getFilteredActions(): object 65 | { 66 | $filteredActions = (object)[]; 67 | $allActions = $this->getAllActions(); 68 | if (isset($allActions->linkData)) { 69 | $this->linkData = $allActions->linkData; 70 | unset($allActions->linkData); 71 | } 72 | foreach ($allActions as $id => $action) { 73 | if ($this->validateTime($action)) { 74 | $filteredActions->{$id} = $action; 75 | } 76 | } 77 | 78 | return $filteredActions; 79 | } 80 | 81 | protected function validateTime(object $actionData): bool 82 | { 83 | $now = time(); 84 | $validFrom = strtotime($actionData->valid_from); 85 | $expiryTime = strtotime($actionData->expiry_time); 86 | 87 | 88 | if ($expiryTime && $expiryTime <= $now) { 89 | return false; 90 | } 91 | 92 | if ($validFrom && $validFrom >= $now) { 93 | return false; 94 | } 95 | 96 | return true; 97 | } 98 | 99 | protected function addLog(string $actionId): self 100 | { 101 | $time = new \DateTime(); 102 | $actions = $this->getAllActions(); 103 | $actions->$actionId->{'last_used'}[] = $time->format('U'); 104 | file_put_contents(self::DATA_DIR . $this->getLink() . '.json', json_encode($actions)); 105 | 106 | return $this; 107 | } 108 | 109 | protected function invalidateAction(object $actionData, string $actionId): self 110 | { 111 | if ($actionData->one_time_use) { 112 | $actions = (array)$this->getAllActions(); 113 | unset($actions[$actionId]); 114 | file_put_contents(self::DATA_DIR . $this->getLink() . '.json', json_encode($actions)); 115 | } 116 | 117 | return $this; 118 | } 119 | 120 | protected function performAction(object $actionData): self 121 | { 122 | $data = (object) array_filter((array) $actionData->service_call_data) ?? []; 123 | $data = json_encode($data); 124 | $serviceCall = explode('.',$actionData->service_call); 125 | 126 | $ch = curl_init(self::API_URL . 'services/' . $serviceCall[0]. '/'. $serviceCall[1]); 127 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); 128 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 129 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 130 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ 131 | "Authorization: Bearer {$_SERVER['SUPERVISOR_TOKEN']}", 132 | 'Content-Type: application/json', 133 | 'Content-Length: ' . mb_strlen($data) 134 | ] 135 | ); 136 | curl_exec($ch); 137 | 138 | return $this; 139 | } 140 | 141 | protected function getLink(): string 142 | { 143 | if (isset($_REQUEST['link']) && ctype_xdigit($_REQUEST['link'])) 144 | return $_REQUEST['link']; 145 | elseif (isset($_REQUEST['link']) 146 | && preg_match('/^([a-zA-Z0-9_-]+)$/', $_REQUEST['link'])) 147 | return $_REQUEST['link']; 148 | else 149 | throw new \Exception('No ID given!'); 150 | } 151 | 152 | protected function getAction(): string 153 | { 154 | if (isset($_REQUEST['action'])) 155 | return $_REQUEST['action']; 156 | else 157 | throw new \Exception('No action given!'); 158 | } 159 | 160 | protected function redirect(string $path): self 161 | { 162 | header("Location: ". $path); 163 | 164 | return $this; 165 | } 166 | 167 | public function getState(string $entityId): bool|string 168 | { 169 | $ch = curl_init(self::API_URL . 'states/'. $entityId); 170 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 171 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ 172 | "Authorization: Bearer {$_SERVER['SUPERVISOR_TOKEN']}" 173 | ] 174 | ); 175 | 176 | return curl_exec($ch); 177 | } 178 | 179 | public function inject($file): ?string 180 | { 181 | foreach (self::INJECT_DIR as $injectDirectory) { 182 | if (file_exists($injectDirectory . $file)) { 183 | if (!preg_match('/^[\w.]+$/', $file)) { 184 | break; 185 | } 186 | 187 | return file_get_contents($injectDirectory . $file); 188 | } 189 | } 190 | 191 | return null; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /limited-guest-access/app/admin/index.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Limited User Access Admin 8 | 9 | 10 | 159 | 312 | 313 | 314 | Create link
315 |
316 |   317 |
318 | Create advanced link 319 |
320 | 322 | 328 | 329 | 330 |
331 |
332 |

333 | 342 |
343 | Add action to link: 344 | 345 |
346 | 347 | 348 | 349 | 350 | 351 |
352 | 353 |
358 |
359 | 360 |
361 | 362 |
363 | 365 |
366 | 367 |
368 | 369 |
370 | 371 |
372 | 383 | 391 | 392 |
393 | 394 |
395 | 404 | 412 | 413 |
414 | 415 |
416 | 417 |
418 | 419 | 420 |
421 |
422 | 469 |

470 | getAllLinks() as $link) : 472 | $link = str_replace([$actions::DATA_DIR,'.json'], '', $link); 473 | $data = json_decode(file_get_contents("/data/links/{$link}.json"),false); 474 | unset($data->linkData); 475 | ?> 476 |
477 |
478 | Link: 484 | 490 | 491 | 492 | 493 | 494 |
495 |
496 | 497 | Add action 498 | 499 | 500 | 501 | 502 |
503 |
504 | 505 | 506 | 507 | 508 | 509 |
510 |
511 | 537 |
538 |
539 | 540 | 541 | 542 | --------------------------------------------------------------------------------