150 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| $configurationFile = '../lucidAuth.config.php';
 | |
| if (!file_exists($configurationFile)) {
 | |
| 	throw new Exception(sprintf('Missing config file. Please rename \'%1$s.example\' to \'%1$s\' and edit it to reflect your setup.', explode('../', $configurationFile)[1]));
 | |
| }
 | |
| $settings = include_once($configurationFile);
 | |
| try {
 | |
| #	switch ($settings->Database['Driver']) {
 | |
| #		case 'sqlite':
 | |
| #			$database = new PDO('sqlite:' . $settings->Database['Path']);
 | |
| 	if (is_writable($settings->Sqlite['Path'])) {
 | |
| 		$pdoDB = new PDO('sqlite:' . $settings->Sqlite['Path']);
 | |
| 	} else {
 | |
| 		throw new Exception(sprintf('Database file \'%1$s\' is not writable', $settings->Sqlite['Path']));
 | |
| 	}
 | |
| #	}
 | |
| }
 | |
| catch (Exception $e) {
 | |
| 	throw new Exception(sprintf('Unable to connect to database \'%1$s\'', $settings->Sqlite['Path']));
 | |
| }
 | |
| 
 | |
| function authenticateLDAP (string $username, string $password) {
 | |
| 	global $settings;
 | |
| 
 | |
| 	if (!empty($username) && !empty($password)) {
 | |
| 		// Handle login requests
 | |
| 
 | |
| 		$ds = ldap_connect($settings->LDAP['Server'], $settings->LDAP['Port']);
 | |
| 
 | |
| 		// Strict namingconvention: only allow alphabetic characters
 | |
| 		$sanitizedUsername = preg_replace('([^a-zA-Z]*)', '', $_POST['username']);
 | |
| 		$qualifiedUsername = $settings->LDAP['Domain'] . '\\' . $sanitizedUsername;
 | |
| 
 | |
| 		if (@ldap_bind($ds, $qualifiedUsername, utf8_encode($_POST['password']))) {
 | |
| 			// Successful authentication; get additional userdetails from authenticationsource
 | |
| 			$ldapSearchResults = ldap_search($ds, $settings->LDAP['BaseDN'], "sAMAccountName=$sanitizedUsername");
 | |
| 			$commonName = ldap_get_entries($ds, $ldapSearchResults)[0]['cn'][0];
 | |
| 			// Create JWT-payload
 | |
| 			$jwtPayload = [
 | |
| 				'iat'	=> time(),					// Issued at: time when the token was generated
 | |
| 				'iss'	=> $_SERVER['SERVER_NAME'],	// Issuer
 | |
| 				'sub'	=> $qualifiedUsername,		// Subject (ie. username)
 | |
| 				'name'	=> $commonName				// Common name (as retrieved from AD)
 | |
| 			];
 | |
| 
 | |
| 			$secureToken = JWT::encode($jwtPayload, base64_decode($settings->JWT['PrivateKey_base64']));
 | |
| 
 | |
| 			return ['status' => 'Success', 'token' => $secureToken];
 | |
| 		} else {
 | |
| 			// LDAP authentication failed!
 | |
| 			return ['status' => 'Fail', 'reason' => '1'];
 | |
| 		}
 | |
| 	} else {
 | |
| 		// Empty username or passwords not allowed!
 | |
| 		return ['status' => 'Fail', 'reason' => '1'];
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function storeToken (string $secureToken, string $qualifiedUsername, string $httpHost) {
 | |
| 	global $settings, $pdoDB;
 | |
| 
 | |
| 	// Save authentication token in database serverside
 | |
| 	try {
 | |
| 		$pdoQuery = $pdoDB->prepare('
 | |
| 			INSERT INTO SecureToken (UserId, Value)
 | |
| 			SELECT User.Id, :securetoken
 | |
| 			FROM User
 | |
| 			WHERE User.Username = :qualifiedusername
 | |
| 		');
 | |
| 		$pdoQuery->execute([
 | |
| 			':securetoken'			=>	$secureToken,
 | |
| 			':qualifiedusername'	=>	$qualifiedUsername
 | |
| 		]);
 | |
| 	}
 | |
| 	catch (Exception $e) {
 | |
| 		return ['status' => 'Fail', 'reason' => $e];
 | |
| 	}
 | |
| 	
 | |
| 	// Save authentication token in cookie clientside
 | |
| 	$cookieDomain = array_values(array_filter($settings->Session['CookieDomains'], function ($value) use ($httpHost) {
 | |
| 		// Check if $_SERVER['HTTP_HOST'] matches any of the configured domains (either explicitly or as a subdomain)
 | |
| 		//   This might seem backwards, but relying on $_SERVER directly allows spoofed values with potential security risks
 | |
| 		return (strlen($value) > strlen($httpHost)) ? false : (0 === substr_compare($httpHost, $value, -strlen($value)));
 | |
| 	}))[0];
 | |
| 	if ($cookieDomain && setcookie('JWT', $secureToken, (time() + $settings->Session['Duration']), '/', '.' . $cookieDomain)) {
 | |
| 		return ['status' => 'Success'];
 | |
| 	} else {
 | |
| 		return ['status' => 'Fail', 'reason' => 'Unable to store cookie(s)'];
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function validateToken (string $secureToken) {
 | |
| 	global $settings, $pdoDB;
 | |
| 
 | |
| 	// Decode provided authentication token
 | |
| 	try {
 | |
| 		$jwtPayload = JWT::decode($secureToken, base64_decode($settings->JWT['PrivateKey_base64']), $settings->JWT['Algorithm']);
 | |
| 	} catch (Exception $e) {
 | |
| 		// Invalid token
 | |
| 		if ($settings->Debug['LogToFile']) {
 | |
| 			file_put_contents('../validateToken.log', (new DateTime())->format('Y-m-d\TH:i:s.u') . ' --- Provided token could not be decoded' . PHP_EOL, FILE_APPEND);
 | |
| 		}
 | |
| 		return ['status' => 'Fail', 'reason' => '1'];
 | |
| 	}
 | |
| 
 | |
| 	if ((int)$jwtPayload->iat < (time() - (int)$settings->Session['Duration'])) {
 | |
| 		// Expired token
 | |
| 		if ($settings->Debug['LogToFile']) {
 | |
| 			file_put_contents('../validateToken.log', (new DateTime())->format('Y-m-d\TH:i:s.u') . ' --- Provided token has expired' . PHP_EOL, FILE_APPEND);
 | |
| 		}
 | |
| 		return ['status' => 'Fail', 'reason' => '3'];
 | |
| 	}
 | |
| 
 | |
| 	// Retrieve all authentication tokens from database matching username
 | |
| 	$pdoQuery = $pdoDB->prepare('
 | |
| 		SELECT SecureToken.Value
 | |
| 		FROM SecureToken
 | |
| 		LEFT JOIN User 
 | |
| 			ON (User.Id=SecureToken.UserId)
 | |
| 		WHERE User.Username = :username
 | |
| 	');
 | |
| 	$pdoQuery->execute([
 | |
| 			':username'	=>	(string)$jwtPayload->sub
 | |
| 		]);
 | |
| 	foreach($pdoQuery->fetchAll(PDO::FETCH_ASSOC) as $row) {
 | |
| 		try {
 | |
| 			$storedTokens[] = JWT::decode($row['Value'], base64_decode($settings->JWT['PrivateKey_base64']), $settings->JWT['Algorithm']);
 | |
| 		} catch (Exception $e) {
 | |
| 			continue;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Compare provided authentication token to all stored tokens in database
 | |
| 	if (!empty($storedTokens) && sizeof(array_filter($storedTokens, function ($value) use ($jwtPayload) {
 | |
| 		return $value->iat === $jwtPayload->iat;
 | |
| 	})) === 1) {
 | |
| 		return [
 | |
| 			'status'	=> 'Success',
 | |
| 			'name'		=> $jwtPayload->name
 | |
| 		];
 | |
| 	} else {
 | |
| 		if ($settings->Debug['LogToFile']) {
 | |
| 			file_put_contents('../validateToken.log', (new DateTime())->format('Y-m-d\TH:i:s.u') . ' --- Either no matching token or multiple matching tokens found in database' . PHP_EOL, FILE_APPEND);
 | |
| 		}
 | |
| 		return ['status' => 'Fail', 'reason' => '2'];
 | |
| 	}
 | |
| }
 | |
| 
 | |
| ?>
 |