浏览代码

Add MultiColumnAuthenticate

dereuromark 8 年之前
父节点
当前提交
b70f59e277

+ 92 - 0
src/Auth/MultiColumnAuthenticate.php

@@ -0,0 +1,92 @@
+<?php
+namespace Tools\Auth;
+
+use Cake\Auth\FormAuthenticate;
+use Cake\Controller\ComponentRegistry;
+use Cake\ORM\TableRegistry;
+
+/**
+ * An authentication adapter for AuthComponent
+ *
+ * Provides the ability to authenticate using POST data. The username form input
+ * can be checked against multiple table columns, for instance username and email
+ *
+ * ```
+ *    $this->Auth->config('authenticate', [
+ *        'Tools.MultiColumn' => [
+ *            'fields' => [
+ *                'username' => 'username',
+ *                'password' => 'password'
+ *             ],
+ *            'columns' => ['username', 'email'],
+ *            'userModel' => 'Users',
+ *            'scope' => ['User.active' => 1]
+ *        ]
+ *    ]);
+ * ```
+ *
+ * Licensed under The MIT License
+ * Copied from discontinued FriendsOfCake/Authenticate
+ */
+class MultiColumnAuthenticate extends FormAuthenticate {
+
+	/**
+	 * Besides the keys specified in BaseAuthenticate::$_defaultConfig,
+	 * MultiColumnAuthenticate uses the following extra keys:
+	 *
+	 * - 'columns' Array of columns to check username form input against
+	 *
+	 * @param \Cake\Controller\ComponentRegistry $registry The Component registry
+	 *   used on this request.
+	 * @param array $config Array of config to use.
+	 */
+	public function __construct(ComponentRegistry $registry, $config) {
+		$this->setConfig([
+			'columns' => [],
+		]);
+
+		parent::__construct($registry, $config);
+	}
+
+	/**
+	 * Get query object for fetching user from database.
+	 *
+	 * @param string $username The username/identifier.
+	 * @return \Cake\ORM\Query
+	 */
+	protected function _query($username) {
+		$table = TableRegistry::get($this->_config['userModel']);
+
+		$columns = [];
+		foreach ($this->_config['columns'] as $column) {
+			$columns[] = [$table->aliasField($column) => $username];
+		}
+		$conditions = ['OR' => $columns];
+
+		$options = [
+			'conditions' => $conditions,
+		];
+
+		if (!empty($this->_config['scope'])) {
+			$options['conditions'] = array_merge($options['conditions'], $this->_config['scope']);
+		}
+		if (!empty($this->_config['contain'])) {
+			$options['contain'] = $this->_config['contain'];
+		}
+
+		$finder = $this->_config['finder'];
+		if (is_array($finder)) {
+			$options += current($finder);
+			$finder = key($finder);
+		}
+
+		if (!isset($options['username'])) {
+			$options['username'] = $username;
+		}
+
+		$query = $table->find($finder, $options);
+
+		return $query;
+	}
+
+}

+ 71 - 0
tests/Fixture/MultiColumnUsersFixture.php

@@ -0,0 +1,71 @@
+<?php
+namespace Tools\Test\Fixture;
+
+use Cake\TestSuite\Fixture\TestFixture;
+
+class MultiColumnUsersFixture extends TestFixture {
+
+	/**
+	 * fields property
+	 *
+	 * @var array
+	 */
+	public $fields = [
+		'id' => ['type' => 'integer'],
+		'user_name' => ['type' => 'string', 'null' => false],
+		'email' => ['type' => 'string', 'null' => false],
+		'password' => ['type' => 'string', 'null' => false],
+		'token' => ['type' => 'string', 'null' => false],
+		'created' => 'datetime',
+		'updated' => 'datetime',
+		'_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
+	];
+
+	/**
+	 * records property
+	 *
+	 * @var array
+	 */
+	public $records = [
+		[
+			'user_name' => 'mariano',
+			'email' => 'mariano@example.com',
+			'password' => '5f4dcc3b5aa765d61d8327deb882cf99',
+			'token' => '12345', 'created' => '2007-03-17 01:16:23',
+			'updated' => '2007-03-17 01:18:31'
+		],
+		[
+			'user_name' => 'nate',
+			'email' => 'nate@example.com',
+			'password' => '5f4dcc3b5aa765d61d8327deb882cf99',
+			'token' => '23456',
+			'created' => '2007-03-17 01:18:23',
+			'updated' => '2007-03-17 01:20:31'
+		],
+		[
+			'user_name' => 'larry',
+			'email' => 'larry@example.com',
+			'password' => '5f4dcc3b5aa765d61d8327deb882cf99',
+			'token' => '34567',
+			'created' => '2007-03-17 01:20:23',
+			'updated' => '2007-03-17 01:22:31'
+		],
+		[
+			'user_name' => 'garrett',
+			'email' => 'garrett@example.com',
+			'password' => '5f4dcc3b5aa765d61d8327deb882cf99',
+			'token' => '45678',
+			'created' => '2007-03-17 01:22:23',
+			'updated' => '2007-03-17 01:24:31'
+		],
+		[
+			'user_name' => 'chartjes',
+			'email' => 'chartjes@example.com',
+			'password' => '5f4dcc3b5aa765d61d8327deb882cf99',
+			'token' => '56789',
+			'created' => '2007-03-17 01:22:23',
+			'updated' => '2007-03-17 01:24:31'
+		]
+	];
+
+}

+ 124 - 0
tests/TestCase/Auth/MultiColumnAuthenticateTest.php

@@ -0,0 +1,124 @@
+<?php
+namespace Tools\Test\TestCase\Auth;
+
+use Cake\I18n\Time;
+use Cake\Network\Request;
+use Cake\ORM\TableRegistry;
+use Cake\TestSuite\TestCase;
+use Tools\Auth\MultiColumnAuthenticate;
+
+class MultiColumnAuthenticateTest extends TestCase {
+
+	/**
+	 * @var array
+	 */
+	public $fixtures = ['plugin.Tools.multi_column_users'];
+
+	/**
+	 * @var \Tools\Auth\MultiColumnAuthenticate
+	 */
+	protected $auth;
+
+	/**
+	 * @var \Cake\Http\Response
+	 */
+	protected $response;
+
+	/**
+	 * @return void
+	 */
+	public function setUp() {
+		parent::setUp();
+
+		$this->Registry = $this->getMockBuilder('Cake\Controller\ComponentRegistry')->getMock();
+		$this->auth = new MultiColumnAuthenticate($this->Registry, [
+			'fields' => ['username' => 'user_name', 'password' => 'password'],
+			'userModel' => 'MultiColumnUsers',
+			'columns' => ['user_name', 'email']
+		]);
+
+		$password = password_hash('password', PASSWORD_DEFAULT);
+		$MultiColumnUsers = TableRegistry::get('MultiColumnUsers');
+		$MultiColumnUsers->updateAll(['password' => $password], []);
+
+		$this->response = $this->getMockBuilder('Cake\Http\Response')->getMock();
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testAuthenticateEmailOrUsername() {
+		$request = new Request('posts/index');
+		$expected = [
+			'id' => 1,
+			'user_name' => 'mariano',
+			'email' => 'mariano@example.com',
+			'token' => '12345',
+			'created' => new Time('2007-03-17 01:16:23'),
+			'updated' => new Time('2007-03-17 01:18:31')
+		];
+
+		$request->data = [
+			'user_name' => 'mariano',
+			'password' => 'password'
+		];
+		$result = $this->auth->authenticate($request, $this->response);
+		$this->assertEquals($expected, $result);
+
+		$request->data = [
+			'user_name' => 'mariano@example.com',
+			'password' => 'password'
+		];
+		$result = $this->auth->authenticate($request, $this->response);
+		$this->assertEquals($expected, $result);
+	}
+	/**
+	 * @return void
+	 */
+	public function testAuthenticateNoUsername() {
+		$request = new Request('posts/index');
+		$request->data = ['password' => 'foobar'];
+		$this->assertFalse($this->auth->authenticate($request, $this->response));
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testAuthenticateNoPassword() {
+		$request = new Request('posts/index');
+		$request->data = ['user_name' => 'mariano'];
+		$this->assertFalse($this->auth->authenticate($request, $this->response));
+
+		$request->data = ['user_name' => 'mariano@example.com'];
+		$this->assertFalse($this->auth->authenticate($request, $this->response));
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testAuthenticateInjection() {
+		$request = new Request('posts/index');
+		$request->data = [
+			'user_name' => '> 1',
+			'password' => "' OR 1 = 1"
+		];
+		$this->assertFalse($this->auth->authenticate($request, $this->response));
+	}
+
+	/**
+	 * test scope failure.
+	 *
+	 * @return void
+	 */
+	public function testAuthenticateScopeFail() {
+		$this->auth->config('scope', ['user_name' => 'nate']);
+		$request = new Request('posts/index');
+		$request->data = [
+			'user_name' => 'mariano',
+			'password' => 'password'
+		];
+
+		$this->assertFalse($this->auth->authenticate($request, $this->response));
+	}
+
+}