Issues (8)

Security Analysis    no request data  

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.

Api/Client.php (3 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
namespace Werkspot\BingAdsApiBundle\Api;
3
4
use BingAds\Proxy\ClientProxy;
5
use BingAds\Reporting\PollGenerateReportRequest;
6
use BingAds\Reporting\ReportRequest;
7
use BingAds\Reporting\ReportTimePeriod;
8
use BingAds\Reporting\SubmitGenerateReportRequest;
9
use Exception;
10
use SoapFault;
11
use SoapVar;
12
use Werkspot\BingAdsApiBundle\Api\Exceptions\InvalidReportNameException;
13
use Werkspot\BingAdsApiBundle\Api\Helper\Csv;
14
use Werkspot\BingAdsApiBundle\Api\Helper\File;
15
use Werkspot\BingAdsApiBundle\Api\Helper\Time;
16
use Werkspot\BingAdsApiBundle\Api\Report\ReportInterface;
17
use Werkspot\BingAdsApiBundle\Guzzle\OauthTokenService;
18
use Werkspot\BingAdsApiBundle\Model\AccessToken;
19
use Werkspot\BingAdsApiBundle\Model\ApiDetails;
20
21
class Client
22
{
23
    const CACHE_SUBDIRECTORY = 'BingAdsApiBundle';
24
    /**
25
     * @var array
26
     */
27
    private $config = [];
28
29
    /**
30
     * @var string
31
     */
32
    private $fileName;
33
34
    /**
35
     * @var ClientProxy
36
     */
37
    private $proxy;
38
39
    /**
40
     * @var array
41
     */
42
    public $report;
43
44
    /**
45
     * @var string[]
46
     */
47
    private $files;
48
49
    /**
50
     * @var OauthTokenService
51
     */
52
    private $oauthTokenService;
53
54
    /**
55
     * @var ApiDetails
56
     */
57
    private $apiDetails;
58
59
    /**
60
     * @var ClientProxy
61
     */
62
    private $clientProxy;
63
64
    /**
65
     * @var File
66
     */
67
    private $fileHelper;
68
69
    /**
70
     * @var Csv
71
     */
72
    private $csvHelper;
73
74
    /**
75
     * @var Time
76
     */
77
    private $timeHelper;
78
79
    /**
80
     * @param OauthTokenService $oauthTokenService
81
     * @param ApiDetails $apiDetails
82
     * @param ClientProxy $clientProxy
83
     * @param File $file
84
     * @param Csv $csv
85
     * @param Time $timeHelper
86
     */
87
    public function __construct(OauthTokenService $oauthTokenService, ApiDetails $apiDetails, ClientProxy $clientProxy, File $file, Csv $csv, Time $timeHelper)
88
    {
89 25
        $this->oauthTokenService = $oauthTokenService;
90 1
        $this->apiDetails = $apiDetails;
91 25
        $this->clientProxy = $clientProxy;
92 25
        $this->fileHelper = $file;
93 25
        $this->csvHelper = $csv;
94 25
        $this->timeHelper = $timeHelper;
95 25
96 25
        ini_set('soap.wsdl_cache_enabled', '0');
97
        ini_set('soap.wsdl_cache_ttl', '0');
98 25
99 25
        $this->fileName = 'report.zip';
100
101 25
        $this->report = [
102
            'GeoLocationPerformanceReport' => new Report\GeoLocationPerformanceReport(),
103 25
        ];
104 25
    }
105
106 25
    public function setApiDetails(ApiDetails $apiDetails)
107
    {
108 1
        $this->apiDetails = $apiDetails;
109
    }
110 1
111 1
    /**
112
     * Sets the configuration
113
     *
114
     * @param $config
115
     */
116
    public function setConfig($config)
117
    {
118 24
        //TODO: make this specific setter
119
        $this->config = $config;
120 24
        $this->config['cache_dir'] = $this->config['cache_dir'] . '/' . self::CACHE_SUBDIRECTORY; //<-- important for the cache clear function
121 24
        $this->config['csv']['fixHeader']['removeColumnHeader'] = true; //-- fix till i know how to do this
122 24
    }
123 24
124
    public function getRefreshToken()
125 1
    {
126
        return $this->apiDetails->getRefreshToken();
127 1
    }
128
129
    /**
130
     * @param string $reportName
131
     * @param array $columns
132
     * @param $timePeriod
133
     * @param null|string $fileLocation
134
     */
135
    public function getReport($reportName, array $columns, $timePeriod = ReportTimePeriod::LastWeek, $fileLocation = null)
136
    {
137
        $this->ensureValidReportName($reportName);
138 24
        $oauthToken = $this->getOauthToken();
139
        $this->apiDetails->setRefreshToken($oauthToken->getRefreshToken());
140 24
141 24
        $report = $this->report[$reportName];
142 24
        $report->setTimePeriod($timePeriod);
143 24
        $report->setColumns($columns);
144 24
        $reportRequest = $report->getRequest();
145 24
        $this->setProxy($report::WSDL, $oauthToken->getAccessToken());
146
        $files = $this->getFilesFromReportRequest($reportRequest, $reportName, "{$this->getCacheDir()}/{$this->fileName}", $report);
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $this instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
147 24
        if ($fileLocation !== null) {
148 24
            $this->fileHelper->moveFirstFile($files, $fileLocation);
149
        }
150 24
        $this->files = $files;
151 24
    }
152 24
153 24
    /**
154 24
     * @return AccessToken
155 24
     */
156
    protected function getOauthToken()
157 3
    {
158 1
        return  $this->oauthTokenService->refreshToken(
159
            $this->apiDetails->getClientId(),
160 1
            $this->apiDetails->getSecret(),
161
            $this->apiDetails->getRedirectUri(),
162 2
            new AccessToken(null, $this->apiDetails->getRefreshToken())
163
        );
164
    }
165
166
    /**
167
     * @param string $wsdl
168
     * @param string $accessToken
169
     */
170 24
    private function setProxy($wsdl, $accessToken)
171
    {
172 24
        $this->proxy = $this->clientProxy->ConstructWithCredentials($wsdl, null, null, $this->apiDetails->getDevToken(), $accessToken);
173 24
    }
174
175
    /**
176
     * @return string
177
     */
178 24
    private function getCacheDir()
179
    {
180 24
        $this->fileHelper->createDirIfNotExists($this->config['cache_dir']);
181 24
182 1
        return $this->config['cache_dir'];
183 1
    }
184
185 24
    /**
186
     * @param ReportRequest $reportRequest
187
     * @param string $name
188
     * @param string $downloadFile
189
     * @param ReportInterface $report
190
     *
191
     * @throws Exception
192
     *
193
     * @return string[]
194
     */
195
    private function getFilesFromReportRequest(ReportRequest $reportRequest, $name, $downloadFile, ReportInterface $report)
196
    {
197
        $reportRequestId = $this->submitGenerateReport($reportRequest, $name);
198 24
        $reportRequestStatus = $this->waitForStatus($reportRequestId);
199
        $reportDownloadUrl = $reportRequestStatus->ReportDownloadUrl;
200 24
        $file = $this->fileHelper->copyFile($reportDownloadUrl, $downloadFile);
201 6
202 3
        if ($this->fileHelper->isHealthyZipFile($file)) {
203 3
            $files = $this->fixFile($report, $this->fileHelper->unZip($file));
204 3
        } else {
205 3
            $files = [$file];
206 3
        }
207 3
208
        return $files;
209 3
    }
210
211
    /**
212
     * SubmitGenerateReport helper method calls the corresponding Bing Ads service operation
213
     * to request the report identifier. The identifier is used to check report generation status
214
     * before downloading the report.
215
     *
216
     * @param mixed  $report
217
     * @param string $name
218
     *
219
     * @return string ReportRequestId
220
     */
221
    private function submitGenerateReport($report, $name)
222 24
    {
223
        $request = new SubmitGenerateReportRequest();
224 24
        try {
225
            $request->ReportRequest = $this->getReportRequest($report, $name);
226 24
227
            return $this->proxy->GetService()->SubmitGenerateReport($request)->ReportRequestId;
228 24
        } catch (SoapFault $e) {
229 18
            $this->parseSoapFault($e);
230 18
        }
231
    }
232
233
    /**
234
     * @param mixed  $report
235
     * @param string $name
236
     *
237
     * @return SoapVar
238
     */
239
    private function getReportRequest($report, $name)
240 24
    {
241
        $name = "{$name}Request";
242 24
243
        return new SoapVar($report, SOAP_ENC_OBJECT, $name, $this->proxy->GetNamespace());
244 24
    }
245
246
    /**
247
     * Check if the report is ready for download
248
     * if not wait 10 sec and retry. (up to 6,5 hour)
249
     * After 30 tries check every 1 minute
250
     * After 34 tries check every 5 minutes
251
     * After 39 tries check every 15 minutes
252
     * After 43 tries check every 30 minutes
253
     *
254
     * @param string  $reportRequestId
255
     * @param int     $count
256
     * @param int     $maxCount
257
     * @param int     $sleep
258
     * @param bool $incrementTime
259
     *
260
     * @throws Exceptions\ReportRequestErrorException
261
     * @throws Exceptions\RequestTimeoutException
262
     *
263
     * @return string
264
     */
265
    private function waitForStatus($reportRequestId, $count = 1, $maxCount = 48, $sleep = 10, $incrementTime = true)
266 6
    {
267
        if ($count > $maxCount) {
268 6
            throw new Exceptions\RequestTimeoutException("The request is taking longer than expected.\nSave the report ID ({$reportRequestId}) and try again later.");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $reportRequestId instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
269 1
        }
270
271
        $reportRequestStatus = $this->pollGenerateReport($reportRequestId);
272 6
        if ($reportRequestStatus->Status == 'Pending') {
273 5
            ++$count;
274 1
            $this->timeHelper->sleep($sleep);
275 1
            if ($incrementTime) {
276 1
                switch ($count) {
277
                    case 31: // after 5 minutes
278 1
                        $sleep = (1 * 60);
279 1
                        break;
280 1
                    case 35: // after 10 minutes
281 1
                        $sleep = (5 * 60);
282 1
                        break;
283 1
                    case 40: // after 30 minutes
284 1
                        $sleep = (15 * 60);
285 1
                        break;
286 1
                    case 44: // after 1,5 hours
287 1
                        $sleep = (30 * 60);
288 1
                        break;
289 1
                }
290
            }
291 1
            $reportRequestStatus = $this->waitForStatus($reportRequestId, $count, $maxCount, $sleep, $incrementTime);
292 1
        }
293
294
        if ($reportRequestStatus->Status == 'Error') {
295 4
            throw new Exceptions\ReportRequestErrorException("The request failed. Try requesting the report later.\nIf the request continues to fail, contact support.", $reportRequestStatus->Status, $reportRequestId);
296 1
        }
297
298
        return $reportRequestStatus;
299 3
    }
300
301
    /**
302
     * Check the status of the report request. The guidance of how often to poll
303
     * for status is from every five to 15 minutes depending on the amount
304
     * of data being requested. For smaller reports, you can poll every couple
305
     * of minutes. You should stop polling and try again later if the request
306
     * is taking longer than an hour.
307
     *
308
     * @param string $reportRequestId
309
     *
310
     * @return string ReportRequestStatus
311
     */
312
    private function pollGenerateReport($reportRequestId)
313 6
    {
314
        $request = new PollGenerateReportRequest();
315 6
        $request->ReportRequestId = $reportRequestId;
316 6
        try {
317
            return $this->proxy->GetService()->PollGenerateReport($request)->ReportRequestStatus;
318 6
        } catch (SoapFault $e) {
319 1
            $this->parseSoapFault($e);
320 1
        }
321
    }
322
323
    /**
324
     * @param array|null $files
325
     *
326
     * @return string[]
327
     */
328
    private function fixFile(ReportInterface $report, array $files)
329 3
    {
330
        foreach ($files as $file) {
331 3
            $lines = $this->fileHelper->readFileLinesIntoArray($file);
332 3
333 3
            $lines = $this->csvHelper->removeHeaders($lines, $this->config['csv']['fixHeader']['removeColumnHeader'], $report::FILE_HEADERS, $report::COLUMN_HEADERS);
334 3
            $lines = $this->csvHelper->removeLastLines($lines);
335 3
            $lines = $this->csvHelper->convertDateMDYtoYMD($lines);
336 3
337 3
            $this->fileHelper->writeLinesToFile($lines, $file);
338 3
        }
339 3
340 3
        return $files;
341
    }
342 3
343
344
    /**
345
     * @param bool $allFiles delete all files in bundles cache, if false deletes only extracted files ($this->files)
346
     *
347
     * @return self
348
     */
349
    public function clearCache($allFiles = false)
350
    {
351
        if ($allFiles) {
352 1
            $this->fileHelper->clearCache($this->config['cache_dir']);
353
        } else {
354 1
            $this->fileHelper->clearCache($this->files);
355 1
        }
356
357 1
        return $this;
358
    }
359
360
    /**
361
     * @param SoapFault $e
362
     *
363
     * @throws Exceptions\SoapInternalErrorException
364
     * @throws Exceptions\SoapInvalidCredentialsException
365
     * @throws Exceptions\SoapNoCompleteDataAvailableException
366
     * @throws Exceptions\SoapReportingServiceInvalidReportIdException
367
     * @throws Exceptions\SoapUnknownErrorException
368
     * @throws Exceptions\SoapUserIsNotAuthorizedException
369
     */
370
    private function parseSoapFault(SoapFault $e)
371
    {
372
        $error = null;
373
        if (isset($e->detail->AdApiFaultDetail)) {
374
            $error = $e->detail->AdApiFaultDetail->Errors->AdApiError;
375
        } elseif (isset($e->detail->ApiFaultDetail)) {
376
            if (!empty($e->detail->ApiFaultDetail->BatchErrors)) {
377
                $error = $error = $e->detail->ApiFaultDetail->BatchErrors->BatchError;
378
            } elseif (!empty($e->detail->ApiFaultDetail->OperationErrors)) {
379
                $error = $e->detail->ApiFaultDetail->OperationErrors->OperationError;
380
            }
381
        }
382
        $errors = is_array($error) ? $error : ['error' => $error];
383
        foreach ($errors as $error) {
384
            switch ($error->Code) {
385
                case 0:
386
                    throw new Exceptions\SoapInternalErrorException($error->Message, $error->Code);
387
                case 105:
388
                    throw new Exceptions\SoapInvalidCredentialsException($error->Message, $error->Code);
389
                case 106:
390
                    throw new Exceptions\SoapUserIsNotAuthorizedException($error->Message, $error->Code);
391
                case 2004:
392
                    throw new Exceptions\SoapNoCompleteDataAvailableException($error->Message, $error->Code);
393
                case 2100:
394
                    throw new Exceptions\SoapReportingServiceInvalidReportIdException($error->Message, $error->Code);
395
                default:
396
                    $errorMessage = "[{$error->Code}]\n{$error->Message}";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $error instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
397 19
                    throw new Exceptions\SoapUnknownErrorException($errorMessage, $error->Code);
398
            }
399 19
        }
400 19
    }
401 7
402 19
    /**
403 12
     * @param $reportName
404 6
     *
405 12
     * @throws InvalidReportNameException
406 6
     */
407 6
    private function ensureValidReportName($reportName)
408 12
    {
409 19
        if ($reportName === '' || $reportName === null) {
410 19
            throw new InvalidReportNameException();
411 19
        }
412 19
    }
413
}
414