Skip to content

Commit

Permalink
Start creating new permissions for registration
Browse files Browse the repository at this point in the history
Introduce a new setting that determines what registration forms are
shown as the pages are accessed:

PUBLIC (or not set) : All registration forms are available and non-users
can register an account
PROJECTADMIN: The public form is hidden and users must be added via
the "Manage project users" or "Manage users" by someone with Project
Administrator priviliges or highter.

ADMIN: Only the Site administrators may register a new account via
the two available forms.

DISABLED: All registration forms are disabled and registration must occur
through a different means (via OAuth/LDAP).
  • Loading branch information
josephsnyder committed Jan 28, 2025
1 parent 0c5b5bb commit fedf702
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 96 deletions.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,13 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
# Whether or not a Project administrator can register a user
# PROJECT_ADMIN_REGISTRATION_FORM_ENABLED=true

# The minimum permission level able to register a user.
# Leaving this value blank will disable registration forms, unless a value can be
# generated from USER_REGISTRATION_FORM_ENABLED and PROJECT_ADMIN_REGISTRATION_FORM_ENABLED

# Options: PUBLIC, PROJECT_ADMIN, ADMIN, DISABLED
# USER_REGISTRATION_ACCESS_LEVEL_REQUIRED=PUBLIC

# Require all new projects to use authenticated submissions.
# Instance administrators can override this, and this setting has no effect on
# existing projects.
Expand Down
11 changes: 11 additions & 0 deletions app/Enums/RegistrationPermissionsLevel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Enums;

enum RegistrationPermissionsLevel: int
{
case PUBLIC = 0;
case PROJECT_ADMIN = 1;
case ADMIN = 2;
case DISABLED = 3;
}
6 changes: 4 additions & 2 deletions app/Http/Controllers/ManageProjectRolesController.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Project as EloquentProject;
Expand Down Expand Up @@ -384,7 +386,7 @@ public function viewPage(): View|RedirectResponse
if ((bool) config('require_full_email_when_adding_user')) {
$xml .= add_XML_value('fullemail', '1');
}
if ((config('auth.project_admin_registration_form_enabled') === true) || $current_user->admin) {
if ($current_user->can('create', [User::class, EloquentProject::find($projectid)])) {
$xml .= add_XML_value('canRegister', '1');
}
$xml .= '</cdash>';
Expand Down Expand Up @@ -426,7 +428,7 @@ private static function find_site_maintainers(int $projectid): array

private function register_user($projectid, $email, $firstName, $lastName, $repositoryCredential)
{
if (config('auth.project_admin_registration_form_enabled') === false) {
if (Gate::authorize('create', [User::class, EloquentProject::findOrFail($projectid)])->denied()) {
return '<error>Users cannot be registered via this form at the current time.</error>';
}

Expand Down
35 changes: 35 additions & 0 deletions app/Policies/UserPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Policies;

use App\Enums\RegistrationPermissionsLevel;
use App\Models\Project;
use App\Models\User;
use Illuminate\Support\Str;

class UserPolicy
{
protected function AttemptValueBuild(): int
{
return (bool) env('USER_REGISTRATION_FORM_ENABLED', true) ? RegistrationPermissionsLevel::PUBLIC->value : ((bool) env('PROJECT_ADMIN_REGISTRATION_FORM_ENABLED', true) ? RegistrationPermissionsLevel::PROJECT_ADMIN->value : RegistrationPermissionsLevel::ADMIN->value);
}

/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
$user_permission_level = Project::whereRelation('administrators', 'users.id', request()->user()?->id)->exists() ? 1 : 0;
$user_permission_level = $user->admin ? 2 : $user_permission_level;
$registration_permission_level_required = match (Str::upper(config('auth.user_registration_access_level_required'))) {
'PUBLIC' => RegistrationPermissionsLevel::PUBLIC->value,
'PROJECT_ADMIN' => RegistrationPermissionsLevel::PROJECT_ADMIN->value,
'ADMIN' => RegistrationPermissionsLevel::ADMIN->value,
'DISABLED' => RegistrationPermissionsLevel::DISABLED->value,
default => $this->AttemptValueBuild(),
};

// Fail if the caller is requesting a value that the setting disallows
return $user_permission_level >= $registration_permission_level_required;
}
}
7 changes: 3 additions & 4 deletions config/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
return [
// Whether or not "normal" username+password authentication is enabled
'username_password_authentication_enabled' => env('USERNAME_PASSWORD_AUTHENTICATION_ENABLED', true),
// Whether or not "normal" username+password authentication is enabled
'user_registration_form_enabled' => env('USER_REGISTRATION_FORM_ENABLED', true),
// Whether or not a Project administrator can register a user
'project_admin_registration_form_enabled' => env('PROJECT_ADMIN_REGISTRATION_FORM_ENABLED', true),
// Which level of permissions can register a user
// supported: PUBLIC,PROJECT_ADMIN,ADMIN,""
'user_registration_access_level_required' => env('USER_REGISTRATION_ACCESS_LEVEL_REQUIRED', (bool) env('USER_REGISTRATION_FORM_ENABLED', true) ? 'PUBLIC' : ((bool) env('PROJECT_ADMIN_REGISTRATION_FORM_ENABLED', true) ? 'PROJECT_ADMIN' : 'ADMIN')),

/*
|--------------------------------------------------------------------------
Expand Down
10 changes: 10 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3719,6 +3719,16 @@ parameters:
count: 2
path: app/Policies/ProjectPolicy.php

-
message: "#^Dynamic call to static method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\<App\\\\Models\\\\Project\\>\\:\\:exists\\(\\)\\.$#"
count: 1
path: app/Policies/UserPolicy.php

-
message: "#^Parameter \\#1 \\$value of static method Illuminate\\\\Support\\\\Str\\:\\:upper\\(\\) expects string, mixed given\\.$#"
count: 1
path: app/Policies/UserPolicy.php

-
message: "#^Dynamic call to static method Illuminate\\\\Foundation\\\\Application\\:\\:isProduction\\(\\)\\.$#"
count: 1
Expand Down
176 changes: 89 additions & 87 deletions resources/views/admin/manage-users.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,93 +40,95 @@
>
</td>
</tr>
<tr>
<td></td>
<td>
<div name="newuser" id="newuser"></div>
</td>
</tr>
<tr>
<td></td>
<td bgcolor="#DDDDDD">
<strong>Add new user</strong>
</td>
</tr>
<tr class="treven">
<td width="20%" height="2" class="nob">
<div align="right"> First Name: </div>
</td>
<td width="80%" height="2" class="nob">
<input class="textbox" name="fname" size="20"/>
</td>
</tr>
<tr class="trodd">
<td width="20%" height="2" class="nob">
<div align="right"> Last Name: </div>
</td>
<td width="80%" height="2" class="nob">
<input class="textbox" name="lname" size="20"/>
</td>
</tr>
<tr class="treven">
<td width="20%" height="2" class="nob">
<div align="right"> Email: </div>
</td>
<td width="80%" height="2" class="nob">
<input class="textbox" name="email" size="20"/>
</td>
</tr>
<tr class="trodd">
<td width="20%" height="2" class="nob">
<div align="right">Password: </div>
</td>
<td width="80%" height="2" class="nob">
<input
class="textbox"
type="password"
id="passwd"
name="passwd"
size="20"
>
<input
type="button"
value="Generate Password"
onclick="javascript:generatePassword();"
name="generatepassword"
class="textbox"
>
<span id="clearpasswd"></span>
</td>
</tr>
<tr class="treven">
<td width="20%" height="2" class="nob">
<div align="right">Confirm Password: </div>
</td>
<td width="80%" height="2" class="nob">
<input
class="textbox"
type="password"
id="passwd2"
name="passwd2"
size="20"
>
</td>
</tr>
<tr class="trodd">
<td width="20%" height="2" class="nob">
<div align="right"> Institution: </div>
</td>
<td width="80%" height="2" class="nob">
<input class="textbox" name="institution" size="20">
</td>
</tr>
<tr>
<td width="20%" class="nob"></td>
<td width="80%" class="nob">
<input type="submit" value="Add user >>" name="adduser" class="textbox"/>
(password will be displayed in clear text upon addition)
</td>
</tr>
@can("create", App\Models\User::class)
<tr>
<td></td>
<td>
<div name="newuser" id="newuser"></div>
</td>
</tr>
<tr>
<td></td>
<td bgcolor="#DDDDDD">
<strong>Add new user</strong>
</td>
</tr>
<tr class="treven">
<td width="20%" height="2" class="nob">
<div align="right"> First Name: </div>
</td>
<td width="80%" height="2" class="nob">
<input class="textbox" name="fname" size="20"/>
</td>
</tr>
<tr class="trodd">
<td width="20%" height="2" class="nob">
<div align="right"> Last Name: </div>
</td>
<td width="80%" height="2" class="nob">
<input class="textbox" name="lname" size="20"/>
</td>
</tr>
<tr class="treven">
<td width="20%" height="2" class="nob">
<div align="right"> Email: </div>
</td>
<td width="80%" height="2" class="nob">
<input class="textbox" name="email" size="20"/>
</td>
</tr>
<tr class="trodd">
<td width="20%" height="2" class="nob">
<div align="right">Password: </div>
</td>
<td width="80%" height="2" class="nob">
<input
class="textbox"
type="password"
id="passwd"
name="passwd"
size="20"
>
<input
type="button"
value="Generate Password"
onclick="javascript:generatePassword();"
name="generatepassword"
class="textbox"
>
<span id="clearpasswd"></span>
</td>
</tr>
<tr class="treven">
<td width="20%" height="2" class="nob">
<div align="right">Confirm Password: </div>
</td>
<td width="80%" height="2" class="nob">
<input
class="textbox"
type="password"
id="passwd2"
name="passwd2"
size="20"
>
</td>
</tr>
<tr class="trodd">
<td width="20%" height="2" class="nob">
<div align="right"> Institution: </div>
</td>
<td width="80%" height="2" class="nob">
<input class="textbox" name="institution" size="20">
</td>
</tr>
<tr>
<td width="20%" class="nob"></td>
<td width="80%" class="nob">
<input type="submit" value="Add user >>" name="adduser" class="textbox"/>
(password will be displayed in clear text upon addition)
</td>
</tr>
@endcan
</table>
</form>

Expand Down
7 changes: 4 additions & 3 deletions resources/views/components/header.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
if (isset($project)) {
$logoid = $project->ImageId;
}
$hideRegistration = config('auth.user_registration_form_enabled') === false;
@endphp
use Illuminate\Support\Str;
$canRegister = Str::upper(config('auth.user_registration_access_level_required')) === "PUBLIC";
@endphp
<div id="header">
<div id="headertop">
<div id="topmenu">
Expand All @@ -18,7 +19,7 @@
<a class="cdash-link" href="{{ url('/logout') }}">Logout</a>
@else
<a class="cdash-link" href="{{ url('/login') }}">Login</a>
@if(!$hideRegistration)
@if($canRegister)
<a class="cdash-link" href="{{ route('register') }}">{{ __('Register') }}</a>
@endif
@endif
Expand Down
21 changes: 21 additions & 0 deletions tests/Environments/.env.ADMIN
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
APP_NAME=CDash
APP_ENV=testing
APP_KEY=DV5SQLqXCbpnme4z2pKNujd6gFW9hrj5
APP_DEBUG=true
APP_URL=http://localhost:8080

LOG_CHANNEL=single

DB_DATABASE=cdash4simpletest
DB_PASSWORD=cdash4simpletest

REGISTRATION_EMAIL_VERIFY=false

QUEUE_CONNECTION=sync
USER_REGISTRATION_ACCESS_LEVEL_REQUIRED=ADMIN
# Disable the slow page warning messages when testing
SLOW_PAGE_TIME=1000000

MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
21 changes: 21 additions & 0 deletions tests/Environments/.env.DISABLED
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
APP_NAME=CDash
APP_ENV=testing
APP_KEY=DV5SQLqXCbpnme4z2pKNujd6gFW9hrj5
APP_DEBUG=true
APP_URL=http://localhost:8080

LOG_CHANNEL=single

DB_DATABASE=cdash4simpletest
DB_PASSWORD=cdash4simpletest

REGISTRATION_EMAIL_VERIFY=false

QUEUE_CONNECTION=sync
USER_REGISTRATION_ACCESS_LEVEL_REQUIRED=DISABLED
# Disable the slow page warning messages when testing
SLOW_PAGE_TIME=1000000

MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
21 changes: 21 additions & 0 deletions tests/Environments/.env.PROJECT_ADMIN
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
APP_NAME=CDash
APP_ENV=testing
APP_KEY=DV5SQLqXCbpnme4z2pKNujd6gFW9hrj5
APP_DEBUG=true
APP_URL=http://localhost:8080

LOG_CHANNEL=single

DB_DATABASE=cdash4simpletest
DB_PASSWORD=cdash4simpletest

REGISTRATION_EMAIL_VERIFY=false

QUEUE_CONNECTION=sync
USER_REGISTRATION_ACCESS_LEVEL_REQUIRED=PROJECT_ADMIN
# Disable the slow page warning messages when testing
SLOW_PAGE_TIME=1000000

MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
Loading

0 comments on commit fedf702

Please sign in to comment.