Issues (850)

Security Analysis    4 potential vulnerabilities

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

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  Code Injection (1)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection (2)
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  Cross-Site Scripting (1)
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.
  Header Injection
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.

includes/gateways/class-getpaid-paypal-api.php (7 issues)

1
<?php
2
3
// Exit if accessed directly.
4
defined( 'ABSPATH' ) || exit;
5
6
/**
7
 * PayPal API handler.
8
 *
9
 * @since 1.0.0
10
 */
11
class GetPaid_PayPal_API {
12
13
	/**
14
	 * Retrieves the bearer token.
15
	 *
16
     * @return string|\WP_Error
17
	 */
18
	public static function get_token( $mode = 'live' ) {
19
20
		$token = get_transient( 'getpaid_paypal_' . $mode . '_token' );
21
22
		if ( $token ) {
23
			return $token;
24
		}
25
26
		$client_id  = 'live' === $mode ? wpinv_get_option( 'paypal_client_id' ) : wpinv_get_option( 'paypal_sandbox_client_id' );
27
		$secret_key = 'live' === $mode ? wpinv_get_option( 'paypal_secret' ) : wpinv_get_option( 'paypal_sandbox_secret' );
28
		$url        = self::get_api_url( 'v1/oauth2/token?grant_type=client_credentials', $mode );
29
30
        if ( empty( $client_id ) || empty( $secret_key ) ) {
31
            return new \WP_Error( 'invalid_request', 'Missing client id or secret key.', array( 'status' => 400 ) );
32
        }
33
34
		$args   = array(
35
			'method'  => 'POST',
36
			'timeout' => 30,
37
			'headers' => array(
38
				'Authorization' => 'Basic ' . base64_encode( $client_id . ':' . $secret_key ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
39
				'Accept'        => 'application/json',
40
				'Content-Type'  => 'application/x-www-form-urlencoded',
41
			),
42
		);
43
44
		$response = self::response_or_error( wp_remote_post( $url, $args ) );
45
46
		if ( is_wp_error( $response ) ) {
47
			return $response;
48
		}
49
50
		if ( ! isset( $response->access_token ) ) {
51
			return new \WP_Error( 'invalid_request', 'Could not create token.', array( 'status' => 400 ) );
52
		}
53
54
		set_transient( 'getpaid_paypal_' . $mode . '_token', $response->access_token, $response->expires_in - 600 );
0 ignored issues
show
The property expires_in does not seem to exist on WP_Error.
Loading history...
55
		return $response->access_token;
56
	}
57
58
	/**
59
	 * Retrieves the PayPal API URL.
60
	 *
61
	 * @param string $endpoint
62
	 * @return string
63
	 */
64
	public static function get_api_url( $endpoint = '', $mode = 'live'  ) {
65
		$endpoint = ltrim( $endpoint, '/' );
66
		return 'live' === $mode ? 'https://api-m.paypal.com/' . $endpoint : 'https://api-m.sandbox.paypal.com/' . $endpoint;
67
	}
68
69
	/**
70
	 * Handles a post request.
71
	 *
72
	 * @param string $path The path to the endpoint.
73
	 * @param mixed $data The data to send.
74
	 * @param string $method The method to use.
75
	 *
76
	 * @return true|\WP_Error
77
	 */
78
	public static function post( $path, $data, $mode = 'live', $method = 'POST' ) {
79
80
		$access_token = self::get_token( $mode );
81
82
		if ( is_wp_error( $access_token ) ) {
83
			return $access_token;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $access_token also could return the type string which is incompatible with the documented return type WP_Error|true.
Loading history...
84
		}
85
86
		$url  = self::get_api_url( $path, $mode );
87
		$args = array(
88
			'method'  => $method,
89
			'headers' => array(
90
				'Authorization' => 'Bearer ' . $access_token,
0 ignored issues
show
Are you sure $access_token of type WP_Error|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
				'Authorization' => 'Bearer ' . /** @scrutinizer ignore-type */ $access_token,
Loading history...
91
				'Content-Type'  => 'application/json',
92
			),
93
		);
94
95
		if( ! empty( $data )) {
96
			$args['body'] = wp_json_encode( $data );
97
		}
98
99
		return self::response_or_error( wp_remote_post( $url, $args ) );
100
	}
101
102
	/**
103
	 * Handles a get request.
104
	 *
105
	 * @param string $path The path to the endpoint.
106
	 * @param string $method
107
	 * @return object|\WP_Error
108
	 */
109
	public static function get( $path, $mode = 'live', $method = 'GET' ) {
110
111
		$access_token = self::get_token( $mode );
112
113
		if ( is_wp_error( $access_token ) ) {
114
			return $access_token;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $access_token also could return the type string which is incompatible with the documented return type WP_Error|object.
Loading history...
115
		}
116
117
		$url  = self::get_api_url( $path, $mode );
118
		$args = array(
119
			'method'  => $method,
120
			'headers' => array(
121
				'Authorization' => 'Bearer ' . $access_token,
0 ignored issues
show
Are you sure $access_token of type WP_Error|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

121
				'Authorization' => 'Bearer ' . /** @scrutinizer ignore-type */ $access_token,
Loading history...
122
			),
123
		);
124
125
		return self::response_or_error( wp_remote_get( $url, $args ) );
126
	}
127
128
	/**
129
	 * Returns the response body
130
	 *
131
	 * @since 1.0.0
132
	 * @version 1.0.0
133
	 * @param \WP_Error|array $response
134
	 * @return \WP_Error|object
135
	 */
136
	public static function response_or_error( $response ) {
137
138
		if ( is_wp_error( $response ) ) {
139
			return new \WP_Error( 'paypal_error', __( 'There was a problem connecting to the PayPal API endpoint.', 'invoicing' ) );
140
		}
141
142
		if ( empty( $response['body'] ) ) {
143
			return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type WP_Error|object.
Loading history...
144
		}
145
146
		$response_body = json_decode( wp_remote_retrieve_body( $response ) );
147
148
		if ( wp_remote_retrieve_response_code( $response ) > 299 ) {
149
150
			// Normal errors.
151
			if ( $response_body && isset( $response_body->message ) ) {
152
				$error_message = $response_body->message;
153
154
			// Identity errors.
155
			} elseif ( $response_body && isset( $response_body->error_description ) ) {
156
				$error_message = $response_body->error_description;
0 ignored issues
show
The assignment to $error_message is dead and can be removed.
Loading history...
157
				return new \WP_Error( 'paypal_error', wp_kses_post( $response_body->error_description ) );
158
			} else {
159
				$error_message = __( 'There was an error connecting to the PayPal API endpoint.', 'invoicing' );
160
			}
161
162
			return new \WP_Error( 'paypal_error', $error_message );
163
		}
164
165
		return $response_body;
166
	}
167
168
	/**
169
	 * Fetches an order.
170
	 *
171
	 * @since 1.0.0
172
	 * @version 1.0.0
173
	 * @param string $order_id
174
	 * @link https://developer.paypal.com/docs/api/orders/v2/#orders_get
175
	 * @return \WP_Error|object
176
	 */
177
	public static function get_order( $order_id, $mode = 'live' ) {
178
		return self::get( '/v2/checkout/orders/' . $order_id, $mode );
179
	}
180
181
	/**
182
	 * Fetches a subscription.
183
	 *
184
	 * @since 1.0.0
185
	 * @version 1.0.0
186
	 * @param string $subscription_id
187
	 * @link https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_get
188
	 * @return \WP_Error|object
189
	 */
190
	public static function get_subscription( $subscription_id, $mode = 'live' ) {
191
		return self::get( '/v1/billing/subscriptions/' . $subscription_id, $mode );
192
	}
193
194
	/**
195
	 * Fetches a subscription's latest transactions (limits search to last one day).
196
	 *
197
	 * @since 1.0.0
198
	 * @version 1.0.0
199
	 * @param string $subscription_id
200
	 * @link https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_transactions
201
	 * @return \WP_Error|object
202
	 */
203
	public static function get_subscription_transaction( $subscription_id, $mode = 'live' ) {
204
		$start_time = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '-1 day' ) );
205
		$end_time   = gmdate( 'Y-m-d\TH:i:s\Z' );
206
		return self::get( "/v1/billing/subscriptions/$subscription_id/transactions?start_time=$start_time&end_time=$end_time", $mode );
207
	}
208
209
	/**
210
	 * Refunds a capture.
211
	 *
212
	 * @since 1.0.0
213
	 * @version 1.0.0
214
	 * @param string $capture_id
215
	 * @param array  $args
216
	 * @link https://developer.paypal.com/docs/api/payments/v2/#captures_refund
217
	 * @return \WP_Error|object
218
	 */
219
	public static function refund_capture( $capture_id, $args = array(), $mode = 'live' ) {
220
		return self::post( '/v2/payments/captures/' . $capture_id . '/refund', $args, $mode );
221
	}
222
223
	/**
224
	 * Cancels a subscription.
225
	 *
226
	 * @since 2.8.24
227
	 * @version 2.8.24
228
	 * @param string $subscription_id
229
	 * @param array  $args
230
	 * @link https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_cancel
231
	 * @return \WP_Error|object
232
	 */
233
	public static function cancel_subscription( $subscription_id, $args = array(), $mode = 'live' ) {
234
		return self::post( '/v1/billing/subscriptions/' . $subscription_id . '/cancel', $args, $mode );
235
	}
236
}
237