Index: trunk/extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php |
— | — | @@ -3,7 +3,7 @@ |
4 | 4 | class PayflowProGateway extends UnlistedSpecialPage { |
5 | 5 | |
6 | 6 | /** |
7 | | - * Defines the action(s) to take on a PFP transaction. |
| 7 | + * Defines the action to take on a PFP transaction. |
8 | 8 | * |
9 | 9 | * Possible values include 'process', 'challenge', |
10 | 10 | * 'review', 'reject'. These values can be set during |
— | — | @@ -12,9 +12,9 @@ |
13 | 13 | * Hooks are exposed to handle the differet actions. |
14 | 14 | * |
15 | 15 | * Defaults to 'process'. |
16 | | - * @var array |
| 16 | + * @var string |
17 | 17 | */ |
18 | | - public $actions = array( 'process' ); |
| 18 | + public $action = NULL; |
19 | 19 | |
20 | 20 | /** |
21 | 21 | * Constructor - set up the new special page |
Index: trunk/extensions/DonationInterface/payflowpro_gateway/extras/minfraud/tests/minfraudTest.php |
— | — | @@ -8,7 +8,7 @@ |
9 | 9 | global $wgMinFraudLog; |
10 | 10 | $wgMinFraudLog = dirname(__FILE__) . "/test_log"; |
11 | 11 | $license_key = 'XBCKSF4gnHA7'; |
12 | | - $this->fixture = new MinFraud( $license_key ); |
| 12 | + $this->fixture = new PayflowProGateway_Extras_MinFraud( $license_key ); |
13 | 13 | } |
14 | 14 | |
15 | 15 | protected function tearDown() { |
— | — | @@ -137,14 +137,13 @@ |
138 | 138 | */ |
139 | 139 | public function testDetermineActions( $risk_score, $action_ranges, $expected ) { |
140 | 140 | $this->fixture->action_ranges = $action_ranges; |
141 | | - $this->assertEquals( $expected, $this->fixture->determine_actions( $risk_score )); |
| 141 | + $this->assertEquals( $expected, $this->fixture->determine_action( $risk_score )); |
142 | 142 | } |
143 | 143 | |
144 | 144 | public function determineActionsData() { |
145 | 145 | return array( |
146 | | - array( '0.1', array( 'process' => array(0, 100)), array('process')), |
147 | | - array( '75.04', array( 'process' => array(0, 50), 'reject' => array( '50.01', '100')), array('reject')), |
148 | | - array( '15', array( 'process' => array(0, 50), 'review' => array(10, 20)), array('process','review')) |
| 146 | + array( '0.1', array( 'process' => array(0, 100)), 'process'), |
| 147 | + array( '75.04', array( 'process' => array(0, 50), 'reject' => array( '50.01', '100')), 'reject'), |
149 | 148 | ); |
150 | 149 | } |
151 | 150 | |
— | — | @@ -155,4 +154,33 @@ |
156 | 155 | $this->assertEquals("foo\n", fread( $new_fh, filesize( $wgMinFraudLog ) )); |
157 | 156 | fclose( $new_fh ); |
158 | 157 | } |
| 158 | + |
| 159 | + public function testGenerateHash() { |
| 160 | + global $wgMinFraudSalt; |
| 161 | + $wgMinFraudSalt = 'salt'; |
| 162 | + $this->assertEquals( '5a9ee1e4a15adbf03b3ef9f7baa6caffa9f6bcd72c736498f045c073e57753e7b244bc97fe82b075eabd80778a4d56eb14406e9a1ac4b13737b2c3fd8c3717e8', $this->fixture->generate_hash( 'foo' )); |
| 163 | + } |
| 164 | + |
| 165 | + public function testCompareHash() { |
| 166 | + global $wgMinFraudSalt; |
| 167 | + $wgMinFraudSalt = 'salt'; |
| 168 | + $this->assertTrue( $this->fixture->compare_hash('5a9ee1e4a15adbf03b3ef9f7baa6caffa9f6bcd72c736498f045c073e57753e7b244bc97fe82b075eabd80778a4d56eb14406e9a1ac4b13737b2c3fd8c3717e8', 'foo')); |
| 169 | + $this->assertFalse( $this->fixture->compare_hash('5a9ee1e4a15adbf03b3ef9f7baa6caffa9f6bcd72c736498f045c073e57753e7b244bc97fe82b075eabd80778a4d56eb14406e9a1ac4b13737b2c3fd8c3717e8', 'bar')); |
| 170 | + } |
| 171 | + |
| 172 | + public function testBypassMinfraud() { |
| 173 | + global $wgMinFraudSalt; |
| 174 | + $wgMinFraudSalt = 'salt'; |
| 175 | + $data = array( |
| 176 | + 'action' => '4bd7857c851039d1e07a434800fe752c6bd99aec61c325aef460441be1b95c3ab5236e43c8d06f41d77715dbd3cf94e679b86422ec3204f00ad433501e5005e9', |
| 177 | + 'data_hash' => '8a42092df24db59b67ab2c6408e00553de93e79dc0d77467f5b91501a392cff99922a0b24f9d0cc5853ae874bbc79c20eb22814ce3a912d743d4137b1f795ac3', |
| 178 | + 'foo' |
| 179 | + ); |
| 180 | + $this->assertTrue( $this->fixture->bypass_minfraud( &$this->fixture, &$data )); |
| 181 | + $this->assertEquals( 'challenge', $this->fixture->action ); |
| 182 | + $this->assertEquals( '8a42092df24db59b67ab2c6408e00553de93e79dc0d77467f5b91501a392cff99922a0b24f9d0cc5853ae874bbc79c20eb22814ce3a912d743d4137b1f795ac3', $data[ 'data_hash' ]); |
| 183 | + |
| 184 | + $data[] = 'bar'; |
| 185 | + $this->assertFalse( $this->fixture->bypass_minfraud( &$this->fixture, &$data )); |
| 186 | + } |
159 | 187 | } |
Index: trunk/extensions/DonationInterface/payflowpro_gateway/extras/minfraud/minfraud.php |
— | — | @@ -55,10 +55,19 @@ |
56 | 56 | * @param object PayflowPro Gateway object |
57 | 57 | * @param array The array of data generated from an attempted transaction |
58 | 58 | */ |
59 | | - public function validate( &$pfp_gateway_object, $data ) { |
| 59 | + public function validate( &$pfp_gateway_object, &$data ) { |
| 60 | + // see if we can bypass minfraud |
| 61 | + if ( $this->bypass_minfraud( $pfp_gateway_object, &$data )) return TRUE; |
| 62 | + |
60 | 63 | $minfraud_hash = $this->build_query( $data ); |
61 | 64 | $this->query_minfraud( $minfraud_hash ); |
62 | | - $pfp_gateway_object->actions = $this->determine_actions( $this->minfraud_response[ 'riskScore' ] ); |
| 65 | + $pfp_gateway_object->action = $this->determine_action( $this->minfraud_response[ 'riskScore' ] ); |
| 66 | + |
| 67 | + // reset the data hash and the action |
| 68 | + if ( isset( $data[ 'data_hash' ] )) unset( $data[ 'data_hash' ] ); |
| 69 | + if ( isset( $data[ 'action' ] )) unset( $data[ 'action' ] ); |
| 70 | + $data[ 'data_hash' ] = $this->genereate_hash( serialize( $data )); |
| 71 | + $data[ 'action' ] = $this->genereate_hash( $pfp_gateway_object->action ); |
63 | 72 | |
64 | 73 | // log the message if the user has specified a log file |
65 | 74 | if ( $this->log_fh ) { |
— | — | @@ -76,6 +85,46 @@ |
77 | 86 | } |
78 | 87 | |
79 | 88 | /** |
| 89 | + * Check to see if we can bypass minFraud check |
| 90 | + * |
| 91 | + * The first time a user hits the submission form, a hash of the full data array plus a |
| 92 | + * hashed action name are injected to the data. This allows us to track the transaction's |
| 93 | + * status. If a valid hash of the data is present and a valid action is present, we can |
| 94 | + * assume the transaction has already gone through the minFraud check and can be passed |
| 95 | + * on to the appropriate action. |
| 96 | + * |
| 97 | + * @param object $pfp_gateway_object The PayflowPro gateway object |
| 98 | + * @param array $data The array of data from the form submission |
| 99 | + * @return bool |
| 100 | + */ |
| 101 | + public function bypass_minfraud( &$pfp_gateway_object, &$data ) { |
| 102 | + // if the data bits data_hash and action are not set, we need to hit minFraud |
| 103 | + if ( isset( $data[ 'data_hash' ] ) && isset( $data[ 'action' ] )) { |
| 104 | + $data_hash = $data[ 'data_hash' ]; // the data hash passed in by the form submission |
| 105 | + $action_hash = $data[ 'action' ]; // a hash of the action to take passed in by the form submission |
| 106 | + |
| 107 | + // unset these values from the data aray since they are not part of the overall data hash |
| 108 | + unset( $data[ 'data_hash' ] ); |
| 109 | + unset( $data[ 'action' ] ); |
| 110 | + // compare the data hash to make sure it's legit |
| 111 | + if ( $this->compare_hash( $data_hash, serialize( $data ))) { |
| 112 | + $data[ 'data_hash' ] = $data_hash; |
| 113 | + |
| 114 | + // check to see if we have a valid action set for us to bypass minfraud |
| 115 | + $actions = array( 'process', 'challenge', 'review', 'reject' ); |
| 116 | + foreach ( $actions as $action ) { |
| 117 | + if ( $this->compare_hash( $action_hash, $action )) { |
| 118 | + // set the action that should be taken |
| 119 | + $pfp_gateway_object->action = $action; |
| 120 | + return TRUE; |
| 121 | + } |
| 122 | + } |
| 123 | + } |
| 124 | + } |
| 125 | + return FALSE; |
| 126 | + } |
| 127 | + |
| 128 | + /** |
80 | 129 | * Get instance of CreditCardFraudDetection |
81 | 130 | * @return object |
82 | 131 | */ |
— | — | @@ -123,8 +172,8 @@ |
124 | 173 | case "bin": // get just the first 6 digits from CC# |
125 | 174 | $newdata[ $value ] = substr( $data[ $value ], 0, 6 ); |
126 | 175 | break; |
127 | | - default: |
128 | | - $newdata[ $value ] = $data[ $value ]; |
| 176 | + default: |
| 177 | + $newdata[ $value ] = $data[ $value ]; |
129 | 178 | } |
130 | 179 | |
131 | 180 | $minfraud_hash[ $key ] = $newdata[ $value ]; |
— | — | @@ -174,22 +223,20 @@ |
175 | 224 | } |
176 | 225 | |
177 | 226 | /** |
178 | | - * Determine the actions for the processor to take |
| 227 | + * Determine the action for the processor to take |
179 | 228 | * |
180 | 229 | * Determined based on predefined riskScore ranges for |
181 | | - * a given action. It is possible to return multiple |
182 | | - * ranges. |
| 230 | + * a given action. |
183 | 231 | * @param float risk score (returned from minFraud) |
184 | 232 | * @return array of actions to be taken |
185 | 233 | */ |
186 | | - public function determine_actions( $risk_score ) { |
| 234 | + public function determine_action( $risk_score ) { |
187 | 235 | $actions = array(); |
188 | 236 | foreach ( $this->action_ranges as $action => $range ) { |
189 | 237 | if ( $risk_score >= $range[0] && $risk_score <= $range[1] ) { |
190 | | - $actions[] = $action; |
| 238 | + return $action; |
191 | 239 | } |
192 | 240 | } |
193 | | - return $actions; |
194 | 241 | } |
195 | 242 | |
196 | 243 | /** |
— | — | @@ -220,6 +267,29 @@ |
221 | 268 | } |
222 | 269 | |
223 | 270 | /** |
| 271 | + * Generate a hash of some data |
| 272 | + * @param string the data to hash |
| 273 | + * @return string The hash of the data |
| 274 | + */ |
| 275 | + public function generate_hash( $data ) { |
| 276 | + global $wgMinFraudSalt; |
| 277 | + return hash( "sha512", $wgMinFraudSalt . $data ); |
| 278 | + } |
| 279 | + |
| 280 | + /** |
| 281 | + * Compare a hash to the hash of some given data |
| 282 | + * @param string $hash A given hash |
| 283 | + * @param strin $data The data to hash and compare to $hash |
| 284 | + * @return bool |
| 285 | + */ |
| 286 | + public function compare_hash( $hash, $data ) { |
| 287 | + if ( $hash == $this->generate_hash( $data )) { |
| 288 | + return TRUE; |
| 289 | + } |
| 290 | + return FALSE; |
| 291 | + } |
| 292 | + |
| 293 | + /** |
224 | 294 | * Close the open log file handler if it's open |
225 | 295 | */ |
226 | 296 | public function __destruct() { |