mirror of
https://github.com/flarum/core.git
synced 2025-08-15 21:04:30 +02:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
17e9bccc15 | ||
|
4a5b84d2e7 | ||
|
557fc2cd39 | ||
|
e92c267cde | ||
|
f959a69530 | ||
|
4e246779f4 | ||
|
5b0f5aeaa0 | ||
|
6e92af8b00 | ||
|
1cf9491fe6 | ||
|
3fcc7bd3b9 | ||
|
4acff91f80 | ||
|
a0152ffb18 | ||
|
d1e38558c5 | ||
|
0cca808275 | ||
|
5ee5f82e3d | ||
|
9077fef5b2 | ||
|
93cebec0be | ||
|
a4a81c0ec2 | ||
|
50dcfdb2a6 | ||
|
8149397850 | ||
|
1ced907e52 | ||
|
17c5a40740 | ||
|
440bed81b8 | ||
|
eeb8fe1443 | ||
|
11b1ab5932 | ||
|
6f34c43dc1 | ||
|
8ced9eef45 | ||
|
8af52153e4 | ||
|
2c5e5f13dd | ||
|
1bbfea4aa3 | ||
|
16f3ae9d1e |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -12,5 +12,6 @@ tests export-ignore
|
|||||||
|
|
||||||
js/dist/* -diff
|
js/dist/* -diff
|
||||||
js/dist/* linguist-generated
|
js/dist/* linguist-generated
|
||||||
|
js/dist-typings/* linguist-generated
|
||||||
|
|
||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,3 +1,2 @@
|
|||||||
github: flarum
|
github: flarum
|
||||||
open_collective: flarum
|
open_collective: flarum
|
||||||
tidelift: packagist/flarum/core
|
|
||||||
|
14
.github/SECURITY.md
vendored
14
.github/SECURITY.md
vendored
@@ -1,13 +1,13 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
## Supported Versions
|
## Versions
|
||||||
|
|
||||||
We will only patch security vulnerabilities in the stable 1.x release.
|
Due to the nature of our project - being open source - we have decided to patch only the latest major release (currently v1.x) for security vulnerabilities.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## How to disclose
|
||||||
|
|
||||||
If you discover a security vulnerability within Flarum, please send an email to security@flarum.org so we can address it promptly.
|
Please use [huntr.dev](https://huntr.dev/) for security issues that affect our project. If you believe you have found a vulnerability, please disclose it via [this form](https://huntr.dev/bounties/disclose/?target=https://github.com/flarum/core).
|
||||||
|
|
||||||
We will get back to you as time allows.
|
This will enable us to **review** the vulnerability, **fix** it promptly, and **reward** you for your efforts.
|
||||||
Discussions may commence internally, so you may not hear back immediately.
|
|
||||||
When reporting a vulnerability, please provide your GitHub username (if available), so that we can invite you to collaborate on a [security advisory on GitHub](https://help.github.com/en/articles/about-maintainer-security-advisories).
|
If you have any questions about the process, feel free to reach out to security@huntr.dev or security@flarum.org.
|
||||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,5 +1,35 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.0.4](https://github.com/flarum/core/compare/v1.0.3...v1.0.4)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Upgrade to v1.0 resets the "view" permission on all tags (https://github.com/flarum/core/pull/2941)
|
||||||
|
|
||||||
|
## [1.0.3](https://github.com/flarum/core/compare/v1.0.2...v1.0.3)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Removed [forum] prefix from Request Password and Email Confirmation emails ([a4a81c0](https://github.com/flarum/core/commit/a4a81c0ec237476cd6e7ca00c1ed9465493af476))
|
||||||
|
- Adopt huntr.dev for handling our security vulnerability reports (https://github.com/flarum/core/pull/2918)
|
||||||
|
- Maintenance handler can now be replaced through the service container (ioc) ([4acff91](https://github.com/flarum/core/commit/4acff91f8063fcced9bf8c9a76fbb510d06823c0))
|
||||||
|
- The colors on the auto generated avatars are now based on the Display Name of the user (https://github.com/flarum/core/pull/2873)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Avatar in notifications list are incorrectly aligned (https://github.com/flarum/core/pull/2906)
|
||||||
|
- FilesystemManager is not compatible with upstream Laravel implementation (https://github.com/flarum/core/pull/2936)
|
||||||
|
|
||||||
|
## [1.0.2](https://github.com/flarum/core/compare/v1.0.1...v1.0.2)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Critical XSS vulnerability
|
||||||
|
|
||||||
|
## [1.0.1](https://github.com/flarum/core/compare/v1.0.0...v1.0.1)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Installation fails on environments without proc_* functions enabled or mysql client binary (https://github.com/flarum/core/issues/2890)
|
||||||
|
|
||||||
## [1.0.0](https://github.com/flarum/core/compare/v0.1.0-beta.16...v1.0.0)
|
## [1.0.0](https://github.com/flarum/core/compare/v0.1.0-beta.16...v1.0.0)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/packagist/dt/flarum/core" alt="Total Downloads"></a>
|
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/packagist/dt/flarum/core" alt="Total Downloads"></a>
|
||||||
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/github/v/release/flarum/core?sort=semver" alt="Latest Version"></a>
|
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/github/v/release/flarum/core?sort=semver" alt="Latest Version"></a>
|
||||||
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/packagist/l/flarum/core" alt="License"></a>
|
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/packagist/l/flarum/core" alt="License"></a>
|
||||||
|
<a href="https://huntr.dev/bounties/disclose/?target=https://github.com/flarum/core"><img src="https://cdn.huntr.dev/huntr_security_badge_mono.svg" alt="huntr"></a>
|
||||||
<a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a>
|
<a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@@ -14,10 +14,26 @@
|
|||||||
"homepage": "https://flarum.org/team"
|
"homepage": "https://flarum.org/team"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/flarum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/flarum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "other",
|
||||||
|
"url": "https://flarum.org/donate"
|
||||||
|
}
|
||||||
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/flarum/core/issues",
|
"issues": "https://github.com/flarum/core/issues",
|
||||||
"source": "https://github.com/flarum/core",
|
"source": "https://github.com/flarum/core",
|
||||||
"docs": "https://flarum.org/docs/"
|
"docs": "https://docs.flarum.org",
|
||||||
|
"forum": "https://discuss.flarum.org",
|
||||||
|
"chat": "https://flarum.org/chat"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.3",
|
"php": ">=7.3",
|
||||||
|
2
js/dist/admin.js
generated
vendored
2
js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
generated
vendored
2
js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js
generated
vendored
2
js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
generated
vendored
2
js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
12
js/package-lock.json
generated
12
js/package-lock.json
generated
@@ -7552,9 +7552,9 @@
|
|||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "7.4.5",
|
"version": "7.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.3.0"
|
"node": ">=8.3.0"
|
||||||
@@ -13723,9 +13723,9 @@
|
|||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "7.4.5",
|
"version": "7.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
@@ -48,12 +48,23 @@ export default class Translator {
|
|||||||
// future there should be a hook here to inspect the user and change the
|
// future there should be a hook here to inspect the user and change the
|
||||||
// translation key. This will allow a gender property to determine which
|
// translation key. This will allow a gender property to determine which
|
||||||
// translation key is used.
|
// translation key is used.
|
||||||
|
|
||||||
if ('user' in parameters) {
|
if ('user' in parameters) {
|
||||||
const user = extract(parameters, 'user');
|
const user = extract(parameters, 'user');
|
||||||
|
|
||||||
if (!parameters.username) parameters.username = username(user);
|
if (!parameters.username) parameters.username = username(user);
|
||||||
}
|
}
|
||||||
return parameters;
|
|
||||||
|
const escapedParameters: TranslatorParameters = {};
|
||||||
|
|
||||||
|
for (const param in parameters) {
|
||||||
|
const paramValue = parameters[param];
|
||||||
|
|
||||||
|
if (typeof paramValue === 'string') escapedParameters[param] = <>{parameters[param]}</>;
|
||||||
|
else escapedParameters[param] = parameters[param];
|
||||||
|
}
|
||||||
|
|
||||||
|
return escapedParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
trans(id: string, parameters: TranslatorParameters = {}) {
|
trans(id: string, parameters: TranslatorParameters = {}) {
|
@@ -35,11 +35,11 @@ Object.assign(User.prototype, {
|
|||||||
canDelete: Model.attribute('canDelete'),
|
canDelete: Model.attribute('canDelete'),
|
||||||
|
|
||||||
avatarColor: null,
|
avatarColor: null,
|
||||||
color: computed('username', 'avatarUrl', 'avatarColor', function (username, avatarUrl, avatarColor) {
|
color: computed('displayName', 'avatarUrl', 'avatarColor', function (displayName, avatarUrl, avatarColor) {
|
||||||
// If we've already calculated and cached the dominant color of the user's
|
// If we've already calculated and cached the dominant color of the user's
|
||||||
// avatar, then we can return that in RGB format. If we haven't, we'll want
|
// avatar, then we can return that in RGB format. If we haven't, we'll want
|
||||||
// to calculate it. Unless the user doesn't have an avatar, in which case
|
// to calculate it. Unless the user doesn't have an avatar, in which case
|
||||||
// we generate a color from their username.
|
// we generate a color from their display name.
|
||||||
if (avatarColor) {
|
if (avatarColor) {
|
||||||
return 'rgb(' + avatarColor.join(', ') + ')';
|
return 'rgb(' + avatarColor.join(', ') + ')';
|
||||||
} else if (avatarUrl) {
|
} else if (avatarUrl) {
|
||||||
@@ -47,7 +47,7 @@ Object.assign(User.prototype, {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '#' + stringToColor(username);
|
return '#' + stringToColor(displayName);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -136,6 +136,14 @@
|
|||||||
.Avatar--size(24px);
|
.Avatar--size(24px);
|
||||||
grid-area: avatar;
|
grid-area: avatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since images don't have baselines, aligning against the baseline won't work.
|
||||||
|
// Instead we need to do some manual hackery to fix then, otherwise they won't
|
||||||
|
// be correctly vertically aligned.
|
||||||
|
img.Avatar {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
&-icon {
|
&-icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@@ -14,23 +14,23 @@ return [
|
|||||||
$db = $schema->getConnection();
|
$db = $schema->getConnection();
|
||||||
|
|
||||||
$db->table('group_permission')
|
$db->table('group_permission')
|
||||||
->where('permission', 'LIKE', 'viewDiscussions')
|
->where('permission', 'LIKE', '%viewDiscussions')
|
||||||
->update(['permission' => $db->raw("REPLACE(permission, 'viewDiscussions', 'viewForum')")]);
|
->update(['permission' => $db->raw("REPLACE(permission, 'viewDiscussions', 'viewForum')")]);
|
||||||
|
|
||||||
$db->table('group_permission')
|
$db->table('group_permission')
|
||||||
->where('permission', 'LIKE', 'viewUserList')
|
->where('permission', 'viewUserList')
|
||||||
->update(['permission' => $db->raw("REPLACE(permission, 'viewUserList', 'searchUsers')")]);
|
->update(['permission' => 'searchUsers']);
|
||||||
},
|
},
|
||||||
|
|
||||||
'down' => function (Builder $schema) {
|
'down' => function (Builder $schema) {
|
||||||
$db = $schema->getConnection();
|
$db = $schema->getConnection();
|
||||||
|
|
||||||
$db->table('group_permission')
|
$db->table('group_permission')
|
||||||
->where('permission', 'LIKE', 'viewForum')
|
->where('permission', 'LIKE', '%viewForum')
|
||||||
->update(['permission' => $db->raw("REPLACE(permission, 'viewForum', 'viewDiscussions')")]);
|
->update(['permission' => $db->raw("REPLACE(permission, 'viewForum', 'viewDiscussions')")]);
|
||||||
|
|
||||||
$db->table('group_permission')
|
$db->table('group_permission')
|
||||||
->where('permission', 'LIKE', 'searchUsers')
|
->where('permission', 'searchUsers')
|
||||||
->update(['permission' => $db->raw("REPLACE(permission, 'searchUsers', 'viewUserList')")]);
|
->update(['permission' => 'viewUserList']);
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@@ -13,8 +13,6 @@ use Exception;
|
|||||||
use Flarum\Extension\Extension;
|
use Flarum\Extension\Extension;
|
||||||
use Illuminate\Database\ConnectionInterface;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Illuminate\Database\MySqlConnection;
|
use Illuminate\Database\MySqlConnection;
|
||||||
use Illuminate\Database\Schema\Builder;
|
|
||||||
use Illuminate\Database\Schema\SchemaState;
|
|
||||||
use Illuminate\Filesystem\Filesystem;
|
use Illuminate\Filesystem\Filesystem;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
@@ -38,31 +36,16 @@ class Migrator
|
|||||||
*/
|
*/
|
||||||
protected $files;
|
protected $files;
|
||||||
|
|
||||||
/**
|
|
||||||
* The database schema builder instance.
|
|
||||||
*
|
|
||||||
* @var Builder
|
|
||||||
*/
|
|
||||||
protected $schemaBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The DB table prefix.
|
|
||||||
*/
|
|
||||||
protected $tablePrefix;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database schema builder instance.
|
|
||||||
*
|
|
||||||
* @var SchemaState
|
|
||||||
*/
|
|
||||||
protected $schemaState;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The output interface implementation.
|
* The output interface implementation.
|
||||||
*
|
*
|
||||||
* @var OutputInterface
|
* @var OutputInterface
|
||||||
*/
|
*/
|
||||||
protected $output;
|
protected $output;
|
||||||
|
/**
|
||||||
|
* @var ConnectionInterface|MySqlConnection
|
||||||
|
*/
|
||||||
|
protected $connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new migrator instance.
|
* Create a new migrator instance.
|
||||||
@@ -83,9 +66,7 @@ class Migrator
|
|||||||
throw new InvalidArgumentException('Only MySQL connections are supported');
|
throw new InvalidArgumentException('Only MySQL connections are supported');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->tablePrefix = $connection->getTablePrefix();
|
$this->connection = $connection;
|
||||||
$this->schemaBuilder = $connection->getSchemaBuilder();
|
|
||||||
$this->schemaState = $connection->getSchemaState();
|
|
||||||
|
|
||||||
// Workaround for https://github.com/laravel/framework/issues/1186
|
// Workaround for https://github.com/laravel/framework/issues/1186
|
||||||
$connection->getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
|
$connection->getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
|
||||||
@@ -218,7 +199,7 @@ class Migrator
|
|||||||
protected function runClosureMigration($migration, $direction = 'up')
|
protected function runClosureMigration($migration, $direction = 'up')
|
||||||
{
|
{
|
||||||
if (is_array($migration) && array_key_exists($direction, $migration)) {
|
if (is_array($migration) && array_key_exists($direction, $migration)) {
|
||||||
call_user_func($migration[$direction], $this->schemaBuilder);
|
call_user_func($migration[$direction], $this->connection->getSchemaBuilder());
|
||||||
} else {
|
} else {
|
||||||
throw new Exception('Migration file should contain an array with up/down.');
|
throw new Exception('Migration file should contain an array with up/down.');
|
||||||
}
|
}
|
||||||
@@ -275,27 +256,31 @@ class Migrator
|
|||||||
{
|
{
|
||||||
$schemaPath = "$path/install.dump";
|
$schemaPath = "$path/install.dump";
|
||||||
|
|
||||||
// If we can't create a tmp file, fall back to the vendor directory.
|
|
||||||
$schemaWithPrefixes = tempnam(sys_get_temp_dir(), 'install');
|
|
||||||
if (! $schemaWithPrefixes) {
|
|
||||||
$schemaWithPrefixes = "$path/install_dump.dump.tmp";
|
|
||||||
}
|
|
||||||
|
|
||||||
$currDumpFile = file_get_contents($schemaPath);
|
|
||||||
|
|
||||||
file_put_contents($schemaWithPrefixes, str_replace('db_prefix_', $this->tablePrefix, $currDumpFile));
|
|
||||||
|
|
||||||
$this->note('<info>Loading stored database schema:</info>');
|
|
||||||
$startTime = microtime(true);
|
$startTime = microtime(true);
|
||||||
|
|
||||||
$this->schemaState->handleOutputUsing(function ($type, $buffer) {
|
$dump = file_get_contents($schemaPath);
|
||||||
$this->output->write($buffer);
|
|
||||||
})->load($schemaWithPrefixes);
|
$this->connection->getSchemaBuilder()->disableForeignKeyConstraints();
|
||||||
|
|
||||||
|
foreach (explode(';', $dump) as $statement) {
|
||||||
|
$statement = trim($statement);
|
||||||
|
|
||||||
|
if (empty($statement) || substr($statement, 0, 2) === '/*') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$statement = str_replace(
|
||||||
|
'db_prefix_',
|
||||||
|
$this->connection->getTablePrefix(),
|
||||||
|
$statement
|
||||||
|
);
|
||||||
|
$this->connection->statement($statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection->getSchemaBuilder()->enableForeignKeyConstraints();
|
||||||
|
|
||||||
$runTime = number_format((microtime(true) - $startTime) * 1000, 2);
|
$runTime = number_format((microtime(true) - $startTime) * 1000, 2);
|
||||||
$this->note('<info>Loaded stored database schema.</info> ('.$runTime.'ms)');
|
$this->note('<info>Loaded stored database schema.</info> ('.$runTime.'ms)');
|
||||||
|
|
||||||
unlink($schemaWithPrefixes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -35,16 +35,16 @@ class FilesystemManager extends LaravelFilesystemManager
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
protected function resolve($name): Filesystem
|
protected function resolve($name, $config = null): Filesystem
|
||||||
{
|
{
|
||||||
$driver = $this->getDriver($name);
|
$localConfig = $config ?? $this->getLocalConfig($name);
|
||||||
|
|
||||||
$localConfig = $this->getLocalConfig($name);
|
|
||||||
|
|
||||||
if (empty($localConfig)) {
|
if (empty($localConfig)) {
|
||||||
throw new InvalidArgumentException("Disk [{$name}] has not been declared. Use the Filesystem extender to do this.");
|
throw new InvalidArgumentException("Disk [{$name}] has not been declared. Use the Filesystem extender to do this.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$driver = $config['driver'] ?? $this->getDriver($name);
|
||||||
|
|
||||||
if ($driver === 'local') {
|
if ($driver === 'local') {
|
||||||
return $this->createLocalDriver($localConfig);
|
return $this->createLocalDriver($localConfig);
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ class Application
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
const VERSION = '1.0.0';
|
const VERSION = '1.0.4';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The IoC container for the Flarum application.
|
* The IoC container for the Flarum application.
|
||||||
|
@@ -48,7 +48,7 @@ class InstalledApp implements AppInterface
|
|||||||
public function getRequestHandler()
|
public function getRequestHandler()
|
||||||
{
|
{
|
||||||
if ($this->config->inMaintenanceMode()) {
|
if ($this->config->inMaintenanceMode()) {
|
||||||
return new MaintenanceModeHandler();
|
return $this->container->make('flarum.maintenance.handler');
|
||||||
} elseif ($this->needsUpdate()) {
|
} elseif ($this->needsUpdate()) {
|
||||||
return $this->getUpdaterHandler();
|
return $this->getUpdaterHandler();
|
||||||
}
|
}
|
||||||
|
@@ -105,6 +105,7 @@ class InstalledSite implements SiteInterface
|
|||||||
$container->alias('flarum.config', Config::class);
|
$container->alias('flarum.config', Config::class);
|
||||||
$container->instance('flarum.debug', $this->config->inDebugMode());
|
$container->instance('flarum.debug', $this->config->inDebugMode());
|
||||||
$container->instance('config', $config = $this->getIlluminateConfig($laravel));
|
$container->instance('config', $config = $this->getIlluminateConfig($laravel));
|
||||||
|
$container->instance('flarum.maintenance.handler', new MaintenanceModeHandler);
|
||||||
|
|
||||||
$this->registerLogger($container);
|
$this->registerLogger($container);
|
||||||
$this->registerCache($container);
|
$this->registerCache($container);
|
||||||
|
@@ -21,8 +21,10 @@ use Flarum\User\UserValidator;
|
|||||||
use Illuminate\Contracts\Events\Dispatcher;
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\Factory;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Intervention\Image\ImageManager;
|
use Intervention\Image\ImageManager;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
class RegisterUserHandler
|
class RegisterUserHandler
|
||||||
{
|
{
|
||||||
@@ -36,12 +38,16 @@ class RegisterUserHandler
|
|||||||
/**
|
/**
|
||||||
* @var UserValidator
|
* @var UserValidator
|
||||||
*/
|
*/
|
||||||
protected $validator;
|
protected $userValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var AvatarUploader
|
* @var AvatarUploader
|
||||||
*/
|
*/
|
||||||
protected $avatarUploader;
|
protected $avatarUploader;
|
||||||
|
/**
|
||||||
|
* @var Factory
|
||||||
|
*/
|
||||||
|
private $validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Dispatcher $events
|
* @param Dispatcher $events
|
||||||
@@ -49,12 +55,13 @@ class RegisterUserHandler
|
|||||||
* @param UserValidator $validator
|
* @param UserValidator $validator
|
||||||
* @param AvatarUploader $avatarUploader
|
* @param AvatarUploader $avatarUploader
|
||||||
*/
|
*/
|
||||||
public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings, UserValidator $validator, AvatarUploader $avatarUploader)
|
public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings, UserValidator $userValidator, AvatarUploader $avatarUploader, Factory $validator)
|
||||||
{
|
{
|
||||||
$this->events = $events;
|
$this->events = $events;
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
$this->validator = $validator;
|
$this->userValidator = $userValidator;
|
||||||
$this->avatarUploader = $avatarUploader;
|
$this->avatarUploader = $avatarUploader;
|
||||||
|
$this->validator = $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,7 +108,7 @@ class RegisterUserHandler
|
|||||||
new Saving($user, $actor, $data)
|
new Saving($user, $actor, $data)
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->validator->assertValid(array_merge($user->getAttributes(), compact('password')));
|
$this->userValidator->assertValid(array_merge($user->getAttributes(), compact('password')));
|
||||||
|
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
@@ -134,8 +141,25 @@ class RegisterUserHandler
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
private function uploadAvatarFromUrl(User $user, string $url)
|
private function uploadAvatarFromUrl(User $user, string $url)
|
||||||
{
|
{
|
||||||
|
$urlValidator = $this->validator->make(compact('url'), [
|
||||||
|
'url' => 'required|active_url',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($urlValidator->fails()) {
|
||||||
|
throw new InvalidArgumentException('Provided avatar URL must be a valid URI.', 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||||
|
|
||||||
|
if (! in_array($scheme, ['http', 'https'])) {
|
||||||
|
throw new InvalidArgumentException("Provided avatar URL must have scheme http or https. Scheme provided was $scheme.", 503);
|
||||||
|
}
|
||||||
|
|
||||||
$image = (new ImageManager)->make($url);
|
$image = (new ImageManager)->make($url);
|
||||||
|
|
||||||
$this->avatarUploader->upload($user, $image);
|
$this->avatarUploader->upload($user, $image);
|
||||||
|
@@ -110,7 +110,7 @@ class RequestPasswordResetHandler
|
|||||||
];
|
];
|
||||||
|
|
||||||
$body = $this->translator->trans('core.email.reset_password.body', $data);
|
$body = $this->translator->trans('core.email.reset_password.body', $data);
|
||||||
$subject = '['.$data['forum'].'] '.$this->translator->trans('core.email.reset_password.subject');
|
$subject = $this->translator->trans('core.email.reset_password.subject');
|
||||||
|
|
||||||
$this->queue->push(new SendRawEmailJob($user->email, $subject, $body));
|
$this->queue->push(new SendRawEmailJob($user->email, $subject, $body));
|
||||||
|
|
||||||
|
@@ -52,7 +52,7 @@ class EmailConfirmationMailer
|
|||||||
$data = $this->getEmailData($event->user, $email);
|
$data = $this->getEmailData($event->user, $email);
|
||||||
|
|
||||||
$body = $this->translator->trans('core.email.confirm_email.body', $data);
|
$body = $this->translator->trans('core.email.confirm_email.body', $data);
|
||||||
$subject = '['.$data['forum'].'] '.$this->translator->trans('core.email.confirm_email.subject');
|
$subject = $this->translator->trans('core.email.confirm_email.subject');
|
||||||
|
|
||||||
$this->queue->push(new SendRawEmailJob($email, $subject, $body));
|
$this->queue->push(new SendRawEmailJob($email, $subject, $body));
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ namespace Flarum\Tests\integration\api\users;
|
|||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||||
use Flarum\Testing\integration\TestCase;
|
use Flarum\Testing\integration\TestCase;
|
||||||
|
use Flarum\User\RegistrationToken;
|
||||||
use Flarum\User\User;
|
use Flarum\User\User;
|
||||||
|
|
||||||
class CreateTest extends TestCase
|
class CreateTest extends TestCase
|
||||||
@@ -168,4 +169,218 @@ class CreateTest extends TestCase
|
|||||||
|
|
||||||
$settings->set('allow_sign_up', true);
|
$settings->set('allow_sign_up', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function cannot_create_user_with_invalid_avatar_uri_scheme()
|
||||||
|
{
|
||||||
|
// Boot app
|
||||||
|
$this->app();
|
||||||
|
|
||||||
|
$regTokens = [];
|
||||||
|
|
||||||
|
// Add registration tokens that should cause a failure
|
||||||
|
$regTokens[] = [
|
||||||
|
'token' => RegistrationToken::generate('flarum', '1', [
|
||||||
|
'username' => 'test',
|
||||||
|
'email' => 'test@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'file://localhost/etc/passwd'
|
||||||
|
], []),
|
||||||
|
'scheme' => 'file'
|
||||||
|
];
|
||||||
|
|
||||||
|
$regTokens[] = [
|
||||||
|
'token' => RegistrationToken::generate('flarum', '1', [
|
||||||
|
'username' => 'test',
|
||||||
|
'email' => 'test@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'ftp://localhost/image.png'
|
||||||
|
], []),
|
||||||
|
'scheme' => 'ftp'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test each reg token
|
||||||
|
foreach ($regTokens as $regToken) {
|
||||||
|
$regToken['token']->saveOrFail();
|
||||||
|
|
||||||
|
// Call the registration endpoint
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request(
|
||||||
|
'POST',
|
||||||
|
'/api/users',
|
||||||
|
[
|
||||||
|
'json' => [
|
||||||
|
'data' => [
|
||||||
|
'attributes' => [
|
||||||
|
'token' => $regToken['token']->token,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)->withAttribute('bypassCsrfToken', true)
|
||||||
|
);
|
||||||
|
|
||||||
|
// The response body should contain details about the invalid URI
|
||||||
|
$body = (string) $response->getBody();
|
||||||
|
$this->assertJson($body);
|
||||||
|
$decodedBody = json_decode($body, true);
|
||||||
|
|
||||||
|
$this->assertEquals(500, $response->getStatusCode());
|
||||||
|
|
||||||
|
$firstError = $decodedBody['errors'][0];
|
||||||
|
|
||||||
|
// Check that the error is an invalid URI
|
||||||
|
$this->assertStringStartsWith('InvalidArgumentException: Provided avatar URL must have scheme http or https. Scheme provided was '.$regToken['scheme'].'.', $firstError['detail']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function cannot_create_user_with_invalid_avatar_uri()
|
||||||
|
{
|
||||||
|
// Boot app
|
||||||
|
$this->app();
|
||||||
|
|
||||||
|
$regTokens = [];
|
||||||
|
|
||||||
|
// Add registration tokens that should cause a failure
|
||||||
|
$regTokens[] = RegistrationToken::generate('flarum', '1', [
|
||||||
|
'username' => 'test',
|
||||||
|
'email' => 'test@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'https://127.0.0.1/image.png'
|
||||||
|
], []);
|
||||||
|
|
||||||
|
$regTokens[] = RegistrationToken::generate('flarum', '1', [
|
||||||
|
'username' => 'test',
|
||||||
|
'email' => 'test@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'https://192.168.0.1/image.png'
|
||||||
|
], []);
|
||||||
|
|
||||||
|
$regTokens[] = RegistrationToken::generate('flarum', '1', [
|
||||||
|
'username' => 'test',
|
||||||
|
'email' => 'test@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => '../image.png'
|
||||||
|
], []);
|
||||||
|
|
||||||
|
$regTokens[] = RegistrationToken::generate('flarum', '1', [
|
||||||
|
'username' => 'test',
|
||||||
|
'email' => 'test@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'image.png'
|
||||||
|
], []);
|
||||||
|
|
||||||
|
// Test each reg token
|
||||||
|
foreach ($regTokens as $regToken) {
|
||||||
|
$regToken->saveOrFail();
|
||||||
|
|
||||||
|
// Call the registration endpoint
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request(
|
||||||
|
'POST',
|
||||||
|
'/api/users',
|
||||||
|
[
|
||||||
|
'json' => [
|
||||||
|
'data' => [
|
||||||
|
'attributes' => [
|
||||||
|
'token' => $regToken->token,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)->withAttribute('bypassCsrfToken', true)
|
||||||
|
);
|
||||||
|
|
||||||
|
// The response body should contain details about the invalid URI
|
||||||
|
$body = (string) $response->getBody();
|
||||||
|
$this->assertJson($body);
|
||||||
|
$decodedBody = json_decode($body, true);
|
||||||
|
|
||||||
|
$this->assertEquals(500, $response->getStatusCode());
|
||||||
|
|
||||||
|
$firstError = $decodedBody['errors'][0];
|
||||||
|
|
||||||
|
// Check that the error is an invalid URI
|
||||||
|
$this->assertStringStartsWith('InvalidArgumentException: Provided avatar URL must be a valid URI.', $firstError['detail']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function can_create_user_with_valid_avatar_uri()
|
||||||
|
{
|
||||||
|
// Boot app
|
||||||
|
$this->app();
|
||||||
|
|
||||||
|
$regTokens = [];
|
||||||
|
|
||||||
|
// Add registration tokens that should work fine
|
||||||
|
$regTokens[] = RegistrationToken::generate('flarum', '1', [
|
||||||
|
'username' => 'test1',
|
||||||
|
'email' => 'test1@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'https://via.placeholder.com/150.png'
|
||||||
|
], []);
|
||||||
|
|
||||||
|
$regTokens[] = RegistrationToken::generate('flarum', '2', [
|
||||||
|
'username' => 'test2',
|
||||||
|
'email' => 'test2@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'https://via.placeholder.com/150.jpg'
|
||||||
|
], []);
|
||||||
|
|
||||||
|
$regTokens[] = RegistrationToken::generate('flarum', '3', [
|
||||||
|
'username' => 'test3',
|
||||||
|
'email' => 'test3@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'https://via.placeholder.com/150.gif'
|
||||||
|
], []);
|
||||||
|
|
||||||
|
$regTokens[] = RegistrationToken::generate('flarum', '4', [
|
||||||
|
'username' => 'test4',
|
||||||
|
'email' => 'test4@machine.local',
|
||||||
|
'is_email_confirmed' => 1,
|
||||||
|
'avatar_url' => 'http://via.placeholder.com/150.png'
|
||||||
|
], []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test each reg token.
|
||||||
|
*
|
||||||
|
* @var RegistrationToken $regToken
|
||||||
|
*/
|
||||||
|
foreach ($regTokens as $regToken) {
|
||||||
|
$regToken->saveOrFail();
|
||||||
|
|
||||||
|
// Call the registration endpoint
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request(
|
||||||
|
'POST',
|
||||||
|
'/api/users',
|
||||||
|
[
|
||||||
|
'json' => [
|
||||||
|
'data' => [
|
||||||
|
'attributes' => [
|
||||||
|
'token' => $regToken->token,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)->withAttribute('bypassCsrfToken', true)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(201, $response->getStatusCode());
|
||||||
|
|
||||||
|
$user = User::where('username', $regToken->user_attributes['username'])->firstOrFail();
|
||||||
|
|
||||||
|
$this->assertEquals($regToken->user_attributes['is_email_confirmed'], $user->is_email_confirmed);
|
||||||
|
$this->assertEquals($regToken->user_attributes['username'], $user->username);
|
||||||
|
$this->assertEquals($regToken->user_attributes['email'], $user->email);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user