Просмотр исходного кода

CLI list-users and create-user

https://github.com/FreshRSS/FreshRSS/issues/1095
https://github.com/FreshRSS/FreshRSS/issues/1090
Alexandre Alapetite 9 лет назад
Родитель
Сommit
e1f214e9e2

+ 64 - 61
app/Controllers/userController.php

@@ -24,6 +24,16 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		}
 	}
 
+	private static function hashPassword($passwordPlain) {
+		if (!function_exists('password_hash')) {
+			include_once(LIB_PATH . '/password_compat.php');
+		}
+		$passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
+		$passwordPlain = '';
+		$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
+		return $passwordHash == '' ? '' : $passwordHash;
+	}
+
 	/**
 	 * This action displays the user profile page.
 	 */
@@ -41,12 +51,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 			if ($passwordPlain != '') {
 				Minz_Request::_param('newPasswordPlain');	//Discard plain-text password ASAP
 				$_POST['newPasswordPlain'] = '';
-				if (!function_exists('password_hash')) {
-					include_once(LIB_PATH . '/password_compat.php');
-				}
-				$passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
-				$passwordPlain = '';
-				$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
+				$passwordHash = self::hashPassword($passwordPlain);
 				$ok &= ($passwordHash != '');
 				FreshRSS_Context::$user_conf->passwordHash = $passwordHash;
 			}
@@ -54,12 +59,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 
 			$passwordPlain = Minz_Request::param('apiPasswordPlain', '', true);
 			if ($passwordPlain != '') {
-				if (!function_exists('password_hash')) {
-					include_once(LIB_PATH . '/password_compat.php');
-				}
-				$passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
-				$passwordPlain = '';
-				$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
+				$passwordHash = self::hashPassword($passwordPlain);
 				$ok &= ($passwordHash != '');
 				FreshRSS_Context::$user_conf->apiPasswordHash = $passwordHash;
 			}
@@ -99,6 +99,53 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		$this->view->size_user = $entryDAO->size();
 	}
 
+	public static function createUser($new_user_name, $passwordPlain, $apiPasswordPlain, $userConfig = array()) {
+		if (!is_array($userConfig)) {
+			$userConfig = array();
+		}
+
+		$ok = ($new_user_name != '') && ctype_alnum($new_user_name);
+
+		if ($ok) {
+			$languages = Minz_Translate::availableLanguages();
+			if (empty($userConfig['language']) || !in_array($userConfig['language'], $languages)) {
+				$userConfig['language'] = 'en';
+			}
+
+			$default_user = FreshRSS_Context::$system_conf->default_user;
+			$ok &= (strcasecmp($new_user_name, $default_user) !== 0);	//It is forbidden to alter the default user
+
+			$ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers()));	//Not an existing user, case-insensitive
+
+			$configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php');
+			$ok &= !file_exists($configPath);
+		}
+		if ($ok) {
+			$passwordHash = '';
+			if ($passwordPlain != '') {
+				$passwordHash = self::hashPassword($passwordPlain);
+				$ok &= ($passwordHash != '');
+			}
+
+			$apiPasswordHash = '';
+			if ($apiPasswordPlain != '') {
+				$apiPasswordHash = self::hashPassword($apiPasswordPlain);
+				$ok &= ($apiPasswordHash != '');
+			}
+		}
+		if ($ok) {
+			mkdir(join_path(DATA_PATH, 'users', $new_user_name));
+			$userConfig['passwordHash'] = $passwordHash;
+			$userConfig['apiPasswordHash'] = $apiPasswordHash;
+			$ok &= (file_put_contents($configPath, "<?php\n return " . var_export($userConfig, true) . ';') !== false);
+		}
+		if ($ok) {
+			$userDAO = new FreshRSS_UserDAO();
+			$ok &= $userDAO->createUser($new_user_name, $userConfig['language']);
+		}
+		return $ok;
+	}
+
 	/**
 	 * This action creates a new user.
 	 *
@@ -116,57 +163,13 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 				FreshRSS_Auth::hasAccess('admin') ||
 				!max_registrations_reached()
 		)) {
-			$db = FreshRSS_Context::$system_conf->db;
-			require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
-
-			$new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language);
-			$languages = Minz_Translate::availableLanguages();
-			if (!in_array($new_user_language, $languages)) {
-				$new_user_language = FreshRSS_Context::$user_conf->language;
-			}
-
 			$new_user_name = Minz_Request::param('new_user_name');
-			$ok = ($new_user_name != '') && ctype_alnum($new_user_name);
-
-			if ($ok) {
-				$default_user = FreshRSS_Context::$system_conf->default_user;
-				$ok &= (strcasecmp($new_user_name, $default_user) !== 0);	//It is forbidden to alter the default user
-
-				$ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers()));	//Not an existing user, case-insensitive
+			$passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true);
+			$new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language);
 
-				$configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php');
-				$ok &= !file_exists($configPath);
-			}
-			if ($ok) {
-				$passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true);
-				$passwordHash = '';
-				if ($passwordPlain != '') {
-					Minz_Request::_param('new_user_passwordPlain');	//Discard plain-text password ASAP
-					$_POST['new_user_passwordPlain'] = '';
-					if (!function_exists('password_hash')) {
-						include_once(LIB_PATH . '/password_compat.php');
-					}
-					$passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
-					$passwordPlain = '';
-					$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
-					$ok &= ($passwordHash != '');
-				}
-				if (empty($passwordHash)) {
-					$passwordHash = '';
-				}
-			}
-			if ($ok) {
-				mkdir(join_path(DATA_PATH, 'users', $new_user_name));
-				$config_array = array(
-					'language' => $new_user_language,
-					'passwordHash' => $passwordHash,
-				);
-				$ok &= (file_put_contents($configPath, "<?php\n return " . var_export($config_array, true) . ';') !== false);
-			}
-			if ($ok) {
-				$userDAO = new FreshRSS_UserDAO();
-				$ok &= $userDAO->createUser($new_user_name, $new_user_language);
-			}
+			$ok = self::createUser($new_user_name, $passwordPlain, '', array('language' => $new_user_language));
+			Minz_Request::_param('new_user_passwordPlain');	//Discard plain-text password ASAP
+			$_POST['new_user_passwordPlain'] = '';
 			invalidateHttpCache();
 
 			$notif = array(

+ 1 - 1
app/Models/Context.php

@@ -37,7 +37,7 @@ class FreshRSS_Context {
 	public static $id_max = '';
 	public static $sinceHours = 0;
 
-	public static $isCron = false;
+	public static $isCli = false;
 
 	/**
 	 * Initialize the context.

+ 1 - 1
app/Models/Feed.php

@@ -141,7 +141,7 @@ class FreshRSS_Feed extends Minz_Model {
 		if (!file_exists($txt)) {
 			file_put_contents($txt, $url);
 		}
-		if (FreshRSS_Context::$isCron) {
+		if (FreshRSS_Context::$isCli) {
 			$ico = $favicons_dir . $this->hash() . '.ico';
 			$ico_mtime = @filemtime($ico);
 			$txt_mtime = @filemtime($txt);

+ 2 - 2
app/actualize_script.php

@@ -28,13 +28,13 @@ $app = new FreshRSS();
 
 $system_conf = Minz_Configuration::get('system');
 $system_conf->auth_type = 'none';  // avoid necessity to be logged in (not saved!)
-FreshRSS_Context::$isCron = true;
+FreshRSS_Context::$isCli = true;
 
 // Create the list of users to actualize.
 // Users are processed in a random order but always start with admin
 $users = listUsers();
 shuffle($users);
-if ($system_conf->default_user !== ''){
+if ($system_conf->default_user !== '') {
 	array_unshift($users, $system_conf->default_user);
 	$users = array_unique($users);
 }

+ 3 - 0
cli/.htaccess

@@ -0,0 +1,3 @@
+Order	Allow,Deny
+Deny	from all
+Satisfy	all

+ 39 - 0
cli/_cli.php

@@ -0,0 +1,39 @@
+<?php
+if (php_sapi_name() !== 'cli') {
+	die('FreshRSS error: This PHP script may only be invoked from command line!');
+}
+
+require(dirname(__FILE__) . '/../constants.php');
+require(LIB_PATH . '/lib_rss.php');
+
+Minz_Configuration::register('system',
+	DATA_PATH . '/config.php',
+	DATA_PATH . '/config.default.php');
+FreshRSS_Context::$system_conf = Minz_Configuration::get('system');
+Minz_Translate::init();
+
+FreshRSS_Context::$isCli = true;
+
+function fail($message) {
+	fwrite(STDERR, $message . "\n");
+	die(1);
+}
+
+function cliInitUser($username) {
+	if (!ctype_alnum($username)) {
+		fail('FreshRSS error: invalid username: ' . $username . "\n");
+	}
+
+	$usernames = listUsers();
+	if (!in_array($username, $usernames)) {
+		fail('FreshRSS error: user not found: ' . $username . "\n");
+	}
+
+	FreshRSS_Context::$user_conf = get_user_configuration($username);
+	if (FreshRSS_Context::$user_conf == null) {
+		fail('FreshRSS error: invalid configuration for user: ' . $username . "\n");
+	}
+	new Minz_ModelPdo($username);
+
+	return $username;
+}

+ 41 - 0
cli/create-user.php

@@ -0,0 +1,41 @@
+#!/usr/bin/php
+<?php
+require('_cli.php');
+
+$options = getopt('', array(
+		'user:',
+		'password::',
+		'api-password::',
+		'language::',
+		'email::',
+		'token::',
+	));
+
+if (empty($options['user'])) {
+	fail('Usage: ' . basename(__FILE__) . " --user=username --password='password' --api-password='api_password'" .
+		" --language=en --email=user@example.net --token='longRandomString'");
+}
+$new_user_name = $options['user'];
+if (!ctype_alnum($new_user_name)) {
+	fail('FreshRSS error: invalid username “' . $new_user_name . '”');
+}
+
+$usernames = listUsers();
+if (preg_grep("/^$new_user_name$/i", $usernames)) {
+	fail('FreshRSS error: username already taken “' . $new_user_name . '”');
+}
+
+echo 'FreshRSS creating user “', $new_user_name, "”…\n";
+
+$ok = FreshRSS_user_Controller::createUser($new_user_name,
+	empty($options['password']) ? '' : $options['password'],
+	empty($options['api-password']) ? '' : $options['api-password'],
+	array(
+		'language' => empty($options['language']) ? '' : $options['language'],
+		'token' => empty($options['token']) ? '' : $options['token'],
+	));
+
+invalidateHttpCache(FreshRSS_Context::$system_conf->default_user);
+
+echo 'Result: ', ($ok ? 'success' : 'fail'), ".\n";
+exit($ok ? 0 : 1);

+ 13 - 0
cli/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
+<head>
+<meta charset="UTF-8" />
+<meta http-equiv="Refresh" content="0; url=/" />
+<title>Redirection</title>
+<meta name="robots" content="noindex" />
+</head>
+
+<body>
+<p><a href="/">Redirection</a></p>
+</body>
+</html>

+ 14 - 0
cli/list-users.php

@@ -0,0 +1,14 @@
+#!/usr/bin/php
+<?php
+require('_cli.php');
+
+$users = listUsers();
+sort($users);
+if ($system_conf->default_user !== '') {
+	array_unshift($users, $system_conf->default_user);
+	$users = array_unique($users);
+}
+
+foreach ($users as $user) {
+	echo $user, "\n";
+}

+ 6 - 3
lib/lib_rss.php

@@ -282,9 +282,12 @@ function uSecString() {
 	return str_pad($t['usec'], 6, '0');
 }
 
-function invalidateHttpCache() {
-	Minz_Session::_param('touch', uTimeString());
-	return touch(join_path(DATA_PATH, 'users', Minz_Session::param('currentUser', '_'), 'log.txt'));
+function invalidateHttpCache($username = '') {
+	if (($username == '') || (!ctype_alnum($username))) {
+		Minz_Session::_param('touch', uTimeString());
+		$username = Minz_Session::param('currentUser', '_');
+	}
+	return touch(join_path(DATA_PATH, 'users', $username, 'log.txt'));
 }
 
 function listUsers() {