Issues (73)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

controllers/FrontendController.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace app\controllers;
4
5
use Yii;
6
use yii\helpers\Url;
7
use yii\web\NotFoundHttpException;
8
use yii\db\Expression;
9
use app\helpers\Alert;
10
use app\models\Screen;
11
use app\models\Device;
12
use app\models\Field;
13
use app\models\Flow;
14
use app\models\Content;
15
use app\models\ContentType;
16
17
/**
18
 * FrontendController implements the actions used by screens.
19
 */
20
class FrontendController extends BaseController
21
{
22
    const ID = 'device_id';
23
    const EXP_YEARS = 10;
24
    public $layout = 'frontend';
25
26
    /**
27
     * Index redirects to associated screen ID.
28
     * Checks authorization based on session & cookie
29
     * Else create a new screen and display auth.
30
     *
31
     * @return string
32
     */
33
    public function actionIndex()
34
    {
35
        $device = $this->getClientDevice();
36
37
        if ($device !== null) { // Associated device
38
            // Check session
39
            if (!$this->isClientAuth()) {
40
                $this->setClientAuth($device);
41
            }
42
43 View Code Duplication
            if (!$device->enabled) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
44
                // Render enable view
45
                return $this->render('err/authorize', [
46
                    'url' => Url::to(['device/view', 'id' => $device->id], true),
47
                ]);
48
            }
49
50
            $screen = $device->getNextScreen();
51 View Code Duplication
            if (!$screen) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
52
                // Render add screen view
53
                return $this->render('err/missing-screen', [
54
                    'url' => Url::to(['device/view', 'id' => $device->id], true),
55
                ]);
56
            }
57
58
            return $this->redirect(['screen', 'id' => $screen->id]);
59
        }
60
61
        // New device
62
        $cookies = Yii::$app->response->cookies;
63
64
        $device = new Device();
65
        $device->name = Yii::$app->request->getUserIP();
66
        $device->description = Yii::t('app', 'New unauthorized device');
67
        $device->save();
68
        $device->id = $device->lastId;
69
70
        $cookies->add(new \yii\web\Cookie([
71
            'name' => self::ID,
72
            'value' => $device->id,
73
            'expire' => time() + (self::EXP_YEARS * 365 * 24 * 60 * 60),
74
        ]));
75
76
        // Render enable view
77
        return $this->render('err/authorize', [
78
            'url' => Url::to(['device/view', 'id' => $device->id], true),
79
        ]);
80
    }
81
82
    /**
83
     * Initializes screen content html structure.
84
     *
85
     * @param int $id screen id
86
     *
87
     * @return \yii\web\Response|string redirect or render
88
     */
89
    public function actionScreen($id, $preview = false)
90
    {
91
        // Session auth
92
        if (!$this->isClientAuth() && !($preview && Yii::$app->user->can('previewScreen'))) {
93
            return $this->redirect(['index']);
94
        }
95
96
        $screen = Screen::find()->where([Screen::tableName().'.id' => $id])->joinWith(['template', 'template.fields', 'template.fields.contentTypes'])->one();
97
        if ($screen === null) {
98
            throw new NotFoundHttpException(Yii::t('app', 'The requested screen does not exist.'));
99
        }
100
        $content = [
101
            'name' => $screen->name,
102
            'screenCss' => $screen->template->css,
103
            'background' => $screen->template->background->uri,
104
            'fields' => $screen->template->fields,
105
            'updateUrl' => $preview ? null : Url::to(['frontend/update', 'id' => $id]),
106
            'nextUrl' => Url::to(['frontend/next', 'id' => $id, 'fieldid' => '']),
107
            'types' => ContentType::getAll(),
108
        ];
109
110
        return $this->render('default', $content);
111
    }
112
113
    /**
114
     * Sends last screen update timestamp, indicating if refresh is needed.
115
     *
116
     * @api
117
     *
118
     * @param int $id screen id
119
     *
120
     * @return string json last update
121
     */
122
    public function actionUpdate($id)
123
    {
124
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
125
        // Session auth
126
        if (!$this->isClientAuth()) { // Disable update if no device association
127
            return ['success' => false, 'message' => 'Unauthorized'];
128
        }
129
130
        $screen = Screen::find()->where([Screen::tableName().'.id' => $id])->one();
131
        if ($screen === null) {
132
            return ['success' => false, 'message' => 'Unknown screen'];
133
        }
134
135
        $device = $this->getClientDevice();
136
        $nextScreen = $device ? $device->getNextScreen($screen->id) : null;
137
138
        return ['success' => true, 'data' => [
139
            'lastChanges' => $screen->last_changes,
140
            'duration' => $screen->duration,
141
            'nextScreenUrl' => $nextScreen ? Url::to(['frontend/screen', 'id' => $nextScreen->id]) : null,
142
        ]];
143
    }
144
145
    /**
146
     * Sends all available content for a specific field.
147
     *
148
     * @param int $id      screen id
149
     * @param int $fieldid field id
150
     *
151
     * @return string json array
152
     */
153
    public function actionNext($id, $fieldid)
154
    {
155
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
156
        // Session auth
157
        if (!$this->isClientAuth() && !Yii::$app->user->can('previewScreen')) {
158
            return ['success' => false, 'message' => 'Unauthorized'];
159
        }
160
161
        // Get screen
162
        $screen = Screen::find()->where([Screen::tableName().'.id' => $id])->joinWith(['flows'])->one();
163
        if ($screen === null) {
164
            return ['success' => false, 'message' => 'Unknown screen'];
165
        }
166
167
        // Get field
168
        $field = Field::find()->where(['id' => $fieldid])->one();
169
        if ($field === null) {
170
            return ['success' => false, 'message' => 'Unknown field'];
171
        }
172
173
        // Get all flows for screen
174
        $flows = $screen->allFlows();
175
176
        // Get all flow ids
177
        $flowIds = array_map(function ($e) {
178
            return $e->id;
179
        }, $flows);
180
181
        // Get all content type ids
182
        $contentTypes = array_map(function ($e) {
183
            return $e->id;
184
        }, $field->contentTypes);
185
186
        // Get content for flows and field type
187
        $contents = Content::find()
188
            ->joinWith(['flow'])
189
            ->where(['type_id' => $contentTypes])
190
            ->andWhere([Flow::tableName().'.id' => $flowIds])
191
            ->andWhere(['enabled' => true])
192
            ->andWhere(['or', ['start_ts' => null], ['<', 'start_ts', new Expression('NOW()')]])
193
            ->andWhere(['or', ['end_ts' => null], ['>', 'end_ts', new Expression('NOW()')]])
194
            ->orderBy('duration ASC')
195
            ->all();
196
197
        $next = array_map(function ($c) use ($field) {
198
            return [
199
                'id' => $c->id,
200
                'data' => $field->mergeData($c->getData()),
201
                'duration' => $c->duration,
202
                'type' => $c->type_id,
203
            ];
204
        }, $contents);
205
206
        return ['success' => true, 'next' => $next];
207
    }
208
209
    /**
210
     * Send an screen reload order to device.
211
     *
212
     * @param int $id screen id
213
     *
214
     * @return \yii\web\Response
215
     */
216
    public function actionForceReload($id)
217
    {
218
        if (Yii::$app->user->can('setScreens')) {
219
            $screen = Screen::findOne($id);
220
            if ($screen !== null) {
221
                Alert::add('Screen will reload', Alert::SUCCESS);
222
                $screen->setModified();
223
224
                return $this->smartGoBack();
225
            }
226
        }
227
228
        Alert::add('Failed to force Screen reload', Alert::DANGER);
229
230
        return $this->smartGoBack();
231
    }
232
233
    /**
234
     * Checks client session for device ID.
235
     *
236
     * @return bool is authenticated
237
     */
238
    private function isClientAuth()
239
    {
240
        return Yii::$app->session->get(self::ID) !== null;
241
    }
242
243
    /**
244
     * Set session with device ID, also add to DB last auth timestamp.
245
     *
246
     * @param \app\models\Device $device
247
     */
248
    private function setClientAuth($device)
249
    {
250
        Yii::$app->session->set(self::ID, $device->id);
251
        $device->setAuthenticated();
252
    }
253
254
    /**
255
     * Look for client device ID from session & cookie.
256
     *
257
     * @return int|null device ID
258
     */
259
    private function getClientId()
260
    {
261
        $id = Yii::$app->session->get(self::ID);
262
        if ($id !== null) {
263
            return $id;
264
        }
265
266
        $cookies = Yii::$app->request->cookies;
267
        $id = $cookies->getValue(self::ID);
268
        if ($id !== null) {
269
            return $id;
270
        }
271
272
        return;
273
    }
274
275
    /**
276
     * Get client device based on session & cookie.
277
     *
278
     * @return \app\models\Device|null device
279
     */
280
    private function getClientDevice()
281
    {
282
        $id = $this->getClientId();
283
        if ($id === null) {
284
            return;
285
        }
286
287
        return Device::findOne($id);
288
    }
289
}
290