Parcourir la source

Adding functionality for authentication using creds from config

John Costanzo il y a 7 ans
Parent
révision
3d7ef32323
4 fichiers modifiés avec 240 ajouts et 44 suppressions
  1. 1
    0
      .gitignore
  2. 35
    1
      README.md
  3. 186
    43
      api.php
  4. 18
    0
      config-template.php

+ 1
- 0
.gitignore Voir le fichier

@@ -3,3 +3,4 @@
3 3
 .settings/
4 4
 phpunit.phar
5 5
 tests/config.php
6
+config.php

+ 35
- 1
README.md Voir le fichier

@@ -21,7 +21,7 @@ This is a single file application! Upload "api.php" somewhere and enjoy!
21 21
 
22 22
 ## Limitations
23 23
 
24
-  - Authentication is not included
24
+  - Authentication is limited to config file only
25 25
   - Composite primary or foreign keys are not supported
26 26
   - Complex filters (with both "and" & "or") are not supported
27 27
   - Complex writes (transactions) are not supported
@@ -47,6 +47,7 @@ This is a single file application! Upload "api.php" somewhere and enjoy!
47 47
   - Relation "transforms" for PHP and JavaScript
48 48
   - Binary fields supported with base64 encoding
49 49
   - Generate API documentation using Swagger tools
50
+  - Enable file based authentication
50 51
 
51 52
 ## Configuration
52 53
 
@@ -96,6 +97,25 @@ $api->executeCommand();
96 97
 
97 98
 NB: The "socket" option is not supported by MS SQL Server. SQLite expects the filename in the "database" field.
98 99
 
100
+Rename config-template.php to config.php and edit the options to your needs
101
+
102
+```
103
+'authentication' => array(
104
+	'enabled' => false, // Set to true if you want auth
105
+	'users' => array( // Set the usernames and password you are willing to accept
106
+		'someusername' => 'somepassword',
107
+		'jusername' => 'jpassword',
108
+	),
109
+	'secret' => 'DFGf9ggdfgDFGDFiikjkjdfg',
110
+	'algorithm' => 'HS256',
111
+	'time' => time(),
112
+	'leeway' => 5,
113
+	'ttl' => 600, // Values is in seconds
114
+	'sub' => '1234567890',
115
+	'admin' => true
116
+)
117
+```
118
+
99 119
 ## Documentation
100 120
 
101 121
 After configuring you can directly benefit from generated API documentation. On the URL below you find the generated API specification in [Swagger](http://swagger.io/) 2.0 format.
@@ -329,6 +349,20 @@ Output:
329 349
 1
330 350
 ```
331 351
 
352
+### Generate Token
353
+If in the config you set enabled to true, under authentication, you can call your api like this:
354
+
355
+```
356
+http://localhost/api.php/getToken?username=youruser&password=yourpass
357
+```
358
+
359
+This will return you some json with a token that you then can use for the the following actiions
360
+
361
+- Create
362
+- Update
363
+- Delete
364
+- Headers
365
+
332 366
 ## Relations
333 367
 
334 368
 The explanation of this feature is based on the data structure from the ```blog.sql``` database file. This database is a very simple blog data structure with corresponding foreign key relations between the tables. These foreign key constraints are required as the relationship detection is based on them, not on column naming.

+ 186
- 43
api.php Voir le fichier

@@ -1,6 +1,8 @@
1 1
 <?php
2 2
 //var_dump($_SERVER['REQUEST_METHOD'],$_SERVER['PATH_INFO']); die();
3 3
 
4
+$configuration = include('config.php');
5
+
4 6
 interface DatabaseInterface {
5 7
 	public function getSql($name);
6 8
 	public function connect($hostname,$username,$password,$database,$port,$socket,$charset);
@@ -764,10 +766,51 @@ class PHP_CRUD_API {
764 766
 	protected $db;
765 767
 	protected $settings;
766 768
 
769
+	protected function getVerifiedClaims($token,$time,$leeway,$ttl,$algorithm,$secret) {
770
+		$algorithms = array('HS256'=>'sha256','HS384'=>'sha384','HS512'=>'sha512');
771
+		if (!isset($algorithms[$algorithm])) return false;
772
+		$hmac = $algorithms[$algorithm];
773
+		$token = explode('.',$token);
774
+		if (count($token)<3) return false;
775
+		$header = json_decode(base64_decode(strtr($token[0],'-_','+/')),true);
776
+		if (!$secret) return false;
777
+		if ($header['typ']!='JWT') return false;
778
+		if ($header['alg']!=$algorithm) return false;
779
+		$signature = bin2hex(base64_decode(strtr($token[2],'-_','+/')));
780
+		if ($signature!=hash_hmac($hmac,"$token[0].$token[1]",$secret)) return false;
781
+		$claims = json_decode(base64_decode(strtr($token[1],'-_','+/')),true);
782
+		if (!$claims) return false;
783
+		if (isset($claims['nbf']) && $time+$leeway<$claims['nbf']) return false;
784
+		if (isset($claims['iat']) && $time+$leeway<$claims['iat']) return false;
785
+		if (isset($claims['exp']) && $time-$leeway>$claims['exp']) return false;
786
+		if (isset($claims['iat']) && !isset($claims['exp'])) {
787
+			if ($time-$leeway>$claims['iat']+$ttl) return false;
788
+		}
789
+		return $claims;
790
+	}
791
+
792
+	protected function generateToken($claims,$time,$ttl,$algorithm,$secret) {
793
+		$algorithms = array('HS256'=>'sha256','HS384'=>'sha384','HS512'=>'sha512');
794
+		$header = array();
795
+		$header['typ']='JWT';
796
+		$header['alg']=$algorithm;
797
+		$token = array();
798
+		$token[0] = rtrim(strtr(base64_encode(json_encode((object)$header)),'+/','-_'),'=');
799
+		$claims['iat'] = $time;
800
+		$claims['exp'] = $time + $ttl;
801
+		$token[1] = rtrim(strtr(base64_encode(json_encode((object)$claims)),'+/','-_'),'=');
802
+		if (!isset($algorithms[$algorithm])) return false;
803
+		$hmac = $algorithms[$algorithm];
804
+		$signature = hash_hmac($hmac,"$token[0].$token[1]",$secret,true);
805
+		$token[2] = rtrim(strtr(base64_encode($signature),'+/','-_'),'=');
806
+		return implode('.',$token);
807
+	}
808
+
767 809
 	protected function mapMethodToAction($method,$key) {
768 810
 		switch ($method) {
769 811
 			case 'OPTIONS': return 'headers';
770
-			case 'GET': return $key?'read':'list';
812
+			case 'GET': return 'read';
813
+			//case 'GET': return $key?'read':'list';
771 814
 			case 'PUT': return 'update';
772 815
 			case 'POST': return 'create';
773 816
 			case 'DELETE': return 'delete';
@@ -1227,10 +1270,64 @@ class PHP_CRUD_API {
1227 1270
 		}
1228 1271
 	}
1229 1272
 
1273
+	protected function setCookie($configAuth) {
1274
+		$user = "token";
1275
+		$claims = array(
1276
+			'sub' => $configAuth["sub"],
1277
+			'name' => $user,
1278
+			'admin' => $configAuth["admin"]
1279
+		);
1280
+
1281
+		if (!isset($_COOKIE[$user])) {
1282
+			$this->settings["authenticated"] = true;
1283
+			$cookie_value = $this->generateToken($claims,
1284
+				$configAuth["time"],$configAuth["ttl"],
1285
+				$configAuth["algorithm"],$configAuth["secret"]);
1286
+
1287
+			$_COOKIE[$user] = $cookie_value;
1288
+			setcookie($user, $cookie_value, time() +
1289
+				$configAuth["ttl"], '/');
1290
+		} else {
1291
+			setcookie($user, $_COOKIE[$user], time() +
1292
+				$configAuth["ttl"], '/');
1293
+		}
1294
+	}
1295
+
1296
+	protected function checkPassword($configAuth, $username, $password) {
1297
+		if ($configAuth['enabled']) {
1298
+			foreach($configAuth['users'] as $user=>$pass) {
1299
+				if ($user == $username && $pass == $password) {
1300
+					return true;
1301
+				}
1302
+			}
1303
+
1304
+			return false;
1305
+		}
1306
+
1307
+		return true;
1308
+	}
1309
+
1230 1310
 	protected function getParameters($settings) {
1231 1311
 		extract($settings);
1232 1312
 
1313
+		if (!isset($configuration)) {
1314
+			$configuration = include('config.php');
1315
+		}
1316
+
1233 1317
 		$table     = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_');
1318
+		$isGetToken = false;
1319
+
1320
+		if ($configuration["authentication"]["enabled"] && $table == "getToken") {
1321
+			$isGetToken = true;
1322
+			$configAuth = $configuration["authentication"];
1323
+			$passedUsername = $this->parseGetParameter($get, 'username', 'a-zA-Z0-9\-_,');
1324
+			$passedPassword = $this->parseGetParameter($get, 'password', 'a-zA-Z0-9\-_,');
1325
+
1326
+			if ($this->checkPassword($configAuth, $passedUsername,
1327
+					$passedPassword)) {
1328
+				$this->setCookie($configAuth);
1329
+			}
1330
+		}
1234 1331
 		$key       = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_'); // auto-increment or uuid
1235 1332
 		$action    = $this->mapMethodToAction($method,$key);
1236 1333
 		$include   = $this->parseGetParameter($get, 'include', 'a-zA-Z0-9\-_,');
@@ -1242,37 +1339,41 @@ class PHP_CRUD_API {
1242 1339
 		$order     = $this->parseGetParameter($get, 'order', 'a-zA-Z0-9\-_,');
1243 1340
 		$transform = $this->parseGetParameter($get, 'transform', 't1');
1244 1341
 
1245
-		$tables    = $this->processTableAndIncludeParameters($database,$table,$include,$action);
1246
-		$key       = $this->processKeyParameter($key,$tables,$database);
1247
-		$filters   = $this->processFiltersParameter($tables,$satisfy,$filters);
1248
-		$page      = $this->processPageParameter($page);
1249
-		$order     = $this->processOrderParameter($order);
1250
-
1251
-		// reflection
1252
-		list($tables,$collect,$select) = $this->findRelations($tables,$database,$auto_include);
1253
-		$columns = $this->addRelationColumns($columns,$select);
1254
-		$fields = $this->findFields($tables,$columns,$database);
1255
-
1256
-		// permissions
1257
-		if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables);
1258
-		if (!isset($tables[0])) $this->exitWith404('entity');
1259
-		if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters);
1260
-		if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters);
1261
-		if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields);
1262
-
1263
-		if ($post) {
1264
-			// input
1265
-			$context = $this->retrieveInput($post);
1266
-			$input = $this->filterInputByFields($context,$fields[$tables[0]]);
1267
-
1268
-			if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]);
1269
-			if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]);
1270
-			if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context);
1342
+		if (!$isGetToken) {
1343
+			$tables    = $this->processTableAndIncludeParameters($database,$table,$include,$action);
1344
+			$key       = $this->processKeyParameter($key,$tables,$database);
1345
+			$filters   = $this->processFiltersParameter($tables,$satisfy,$filters);
1346
+			$page      = $this->processPageParameter($page);
1347
+			$order     = $this->processOrderParameter($order);
1348
+
1349
+			// reflection
1350
+			list($tables,$collect,$select) = $this->findRelations($tables,$database,$auto_include);
1351
+			$columns = $this->addRelationColumns($columns,$select);
1352
+			$fields = $this->findFields($tables,$columns,$database);
1353
+
1354
+			// permissions
1355
+			if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables);
1356
+			if (!isset($tables[0])) $this->exitWith404('entity');
1357
+			if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters);
1358
+			if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters);
1359
+			if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields);
1360
+
1361
+			if ($post) {
1362
+				// input
1363
+				$context = $this->retrieveInput($post);
1364
+				$input = $this->filterInputByFields($context,$fields[$tables[0]]);
1365
+
1366
+				if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]);
1367
+				if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]);
1368
+				if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context);
1369
+
1370
+				$this->convertBinary($input,$fields[$tables[0]]);
1371
+			}
1271 1372
 
1272
-			$this->convertBinary($input,$fields[$tables[0]]);
1373
+			return compact('action','database','tables','key','callback','page','filters','fields','order','transform','input','collect','select');
1374
+		} else {
1375
+			return array('token');
1273 1376
 		}
1274
-
1275
-		return compact('action','database','tables','key','callback','page','filters','fields','order','transform','input','collect','select');
1276 1377
 	}
1277 1378
 
1278 1379
 	protected function addWhereFromFilters($filters,&$sql,&$params) {
@@ -1482,6 +1583,22 @@ class PHP_CRUD_API {
1482 1583
 		$this->endOutput($callback);
1483 1584
 	}
1484 1585
 
1586
+	protected function tokenCommand() {
1587
+		$callback = "";
1588
+		$this->startOutput($callback);
1589
+		$json = '{"token": "' .$_COOKIE["token"]. '"}';
1590
+		echo $json;
1591
+		$this->endOutput($callback);
1592
+	}
1593
+
1594
+	protected function invalidToken() {
1595
+		$callback = "";
1596
+		$this->startOutput($callback);
1597
+		$json = '{"error": "Your token is invalid or it has expired"}';
1598
+		echo $json;
1599
+		$this->endOutput($callback);
1600
+	}
1601
+
1485 1602
 	public function __construct($config) {
1486 1603
 		extract($config);
1487 1604
 
@@ -1619,7 +1736,7 @@ class PHP_CRUD_API {
1619 1736
 			$table_list = array($table['name']);
1620 1737
 			$table_fields = $this->findFields($table_list,false,$database);
1621 1738
 			$table_names = array_map(function($v){ return $v['name'];},$tables);
1622
-			
1739
+
1623 1740
 			if ($extensions) {
1624 1741
 				$result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table_list[0],$table_names,$database,$database));
1625 1742
 				while ($row = $this->db->fetchRow($result)) {
@@ -1634,7 +1751,7 @@ class PHP_CRUD_API {
1634 1751
 					$table_fields[$table['name']][$primaryKey]->primaryKey = true;
1635 1752
 				}
1636 1753
 			}
1637
-			
1754
+
1638 1755
 			foreach (array('root_actions','id_actions') as $path) {
1639 1756
 				foreach ($table[$path] as $i=>$action) {
1640 1757
 					$table_list = array($table['name']);
@@ -1912,17 +2029,43 @@ class PHP_CRUD_API {
1912 2029
 			$this->swagger($this->settings);
1913 2030
 		} else {
1914 2031
 			$parameters = $this->getParameters($this->settings);
1915
-			switch($parameters['action']){
1916
-				case 'list': $this->listCommand($parameters); break;
1917
-				case 'read': $this->readCommand($parameters); break;
1918
-				case 'create': $this->createCommand($parameters); break;
1919
-				case 'update': $this->updateCommand($parameters); break;
1920
-				case 'delete': $this->deleteCommand($parameters); break;
1921
-				case 'headers': $this->headersCommand($parameters); break;
2032
+
2033
+			if(isset($parameters[0]) && $parameters[0] == 'token') {
2034
+				$this->tokenCommand();
2035
+			} else {
2036
+				if (!isset($configuration)) {
2037
+					$configuration = include('config.php');
2038
+				}
2039
+
2040
+				if ($parameters['action'] == 'list') {
2041
+					$this->listCommand($parameters);
2042
+				} else if ($parameters['action'] == 'read') {
2043
+					$this->readCommand($parameters);
2044
+				} else if($configuration['authentication']['enabled']) {
2045
+					if (isset($_COOKIE["token"]) &&
2046
+							($_COOKIE["token"] == $this->settings["get"]["token"])) {
2047
+						$this->setCookie($configuration["authentication"]);
2048
+
2049
+						switch($parameters['action']) {
2050
+							case 'create': $this->createCommand($parameters); break;
2051
+							case 'update': $this->updateCommand($parameters); break;
2052
+							case 'delete': $this->deleteCommand($parameters); break;
2053
+							case 'headers': $this->headersCommand($parameters); break;
2054
+						}
2055
+					} else {
2056
+						$this->invalidToken();
2057
+					}
2058
+				} else {
2059
+					switch($parameters['action']) {
2060
+						case 'create': $this->createCommand($parameters); break;
2061
+						case 'update': $this->updateCommand($parameters); break;
2062
+						case 'delete': $this->deleteCommand($parameters); break;
2063
+						case 'headers': $this->headersCommand($parameters); break;
2064
+					}
2065
+				}
1922 2066
 			}
1923 2067
 		}
1924 2068
 	}
1925
-
1926 2069
 }
1927 2070
 
1928 2071
 // uncomment the lines below when running in stand-alone mode:
@@ -1930,9 +2073,9 @@ class PHP_CRUD_API {
1930 2073
 // $api = new PHP_CRUD_API(array(
1931 2074
 // 	'dbengine'=>'MySQL',
1932 2075
 // 	'hostname'=>'localhost',
1933
-//	'username'=>'xxx',
1934
-//	'password'=>'xxx',
1935
-//	'database'=>'xxx',
2076
+// 	'username'=>'xxx',
2077
+// 	'password'=>'xxx',
2078
+// 	'database'=>'xxx',
1936 2079
 // 	'charset'=>'utf8'
1937 2080
 // ));
1938 2081
 // $api->executeCommand();

+ 18
- 0
config-template.php Voir le fichier

@@ -0,0 +1,18 @@
1
+<?php
2
+	return array(
3
+		'authentication' => array(
4
+			'enabled' => false, // Set to true if you want auth
5
+			'users' => array( // Set the usernames and password you are willing to accept
6
+				'someusername' => 'somepassword',
7
+				'jusername' => 'jpassword',
8
+			),
9
+			'secret' => 'DFGf9ggdfgDFGDFiikjkjdfg',
10
+			'algorithm' => 'HS256',
11
+			'time' => time(),
12
+			'leeway' => 5,
13
+			'ttl' => 600, // Values is in seconds
14
+			'sub' => '1234567890',
15
+			'admin' => true
16
+		)
17
+	);
18
+?>

Loading…
Annuler
Enregistrer