Skip to content

Commit e0a7e73

Browse files
Merge pull request #8472 from christianbeeznest/fixes-plugin-userremoteservice01
Plugin: Improve UserRemoteService plugin safety and UI
2 parents 7458a9c + 756ca8f commit e0a7e73

10 files changed

Lines changed: 484 additions & 274 deletions

File tree

public/main/admin/settings.lib.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ function getStablePluginAllowList(): array
330330
'LearningCalendar',
331331
'SurveyExportCsv',
332332
'SurveyExportTxt',
333+
'UserRemoteService',
333334
];
334335
}
335336

public/plugin/UserRemoteService/Entity/UserRemoteService.php

Lines changed: 30 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,80 @@
11
<?php
22
/* For licensing terms, see /license.txt */
33

4+
declare(strict_types=1);
5+
46
namespace Chamilo\PluginBundle\UserRemoteService;
57

68
use Doctrine\ORM\Mapping as ORM;
79
use Exception;
810

9-
/**
10-
* UserRemoteService.
11-
*
12-
* @ORM\Table(name="plugin_user_remote_service")
13-
* @ORM\Entity
14-
*/
11+
#[ORM\Table(name: 'plugin_user_remote_service')]
12+
#[ORM\Entity]
1513
class UserRemoteService
1614
{
17-
/**
18-
* @var int
19-
*
20-
* @ORM\Column(name="id", type="integer")
21-
* @ORM\Id
22-
* @ORM\GeneratedValue
23-
*/
24-
protected $id;
15+
#[ORM\Column(name: 'id', type: 'integer')]
16+
#[ORM\Id]
17+
#[ORM\GeneratedValue]
18+
protected ?int $id = null;
2519

26-
/**
27-
* @var string
28-
*
29-
* @ORM\Column(name="title", type="string", length=255, nullable=false)
30-
*/
31-
protected $title;
20+
#[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)]
21+
protected string $title = '';
3222

33-
/**
34-
* @var string
35-
*
36-
* @ORM\Column(name="url", type="string", length=255, nullable=false)
37-
*/
38-
protected $url;
23+
#[ORM\Column(name: 'url', type: 'string', length: 255, nullable: false)]
24+
protected string $url = '';
3925

40-
/**
41-
* @return int
42-
*/
43-
public function getId()
26+
public function getId(): ?int
4427
{
4528
return $this->id;
4629
}
4730

48-
/**
49-
* @param int $id
50-
*
51-
* @return $this
52-
*/
53-
public function setId($id)
31+
public function setId(int $id): self
5432
{
5533
$this->id = $id;
5634

5735
return $this;
5836
}
5937

60-
/**
61-
* @return string
62-
*/
63-
public function getTitle()
38+
public function getTitle(): string
6439
{
6540
return $this->title;
6641
}
6742

68-
/**
69-
* @param string $title
70-
*
71-
* @return UserRemoteService
72-
*/
73-
public function setTitle($title)
43+
public function setTitle(string $title): self
7444
{
7545
$this->title = $title;
7646

7747
return $this;
7848
}
7949

80-
/**
81-
* @return string
82-
*/
83-
public function getURL()
50+
public function getURL(): string
8451
{
8552
return $this->url;
8653
}
8754

88-
/**
89-
* @param string $url
90-
*
91-
* @return UserRemoteService
92-
*/
93-
public function setURL($url)
55+
public function setURL(string $url): self
9456
{
9557
$this->url = $url;
9658

9759
return $this;
9860
}
9961

100-
/**
101-
* @return string
102-
*/
103-
public function getAccessURL($pluginName)
62+
public function getAccessURL(string $pluginName): string
10463
{
105-
$accessUrl = api_get_path(WEB_PLUGIN_PATH).$pluginName."/redirect.php?serviceId=".$this->getId();
106-
107-
return $accessUrl;
64+
return api_get_path(WEB_PLUGIN_PATH).$pluginName.'/redirect.php?serviceId='.(int) $this->getId();
10865
}
10966

11067
/**
111-
* Returns a user-specific URL, with two extra query string parameters : 'username' and 'hash'.
112-
* 'hash' is generated using $salt and $userId.
113-
*
114-
* @param string $username the URL query parameter 'username'
115-
* @param string $userId the user identifier, to build the hash
116-
* @param string $salt the salt, to build the hash
117-
*
118-
* @throws Exception on hash generation failure
68+
* Returns a user-specific URL with username and hash parameters.
11969
*
120-
* @return string the custom user URL
70+
* @throws Exception
12171
*/
122-
public function getCustomUserURL($username, $userId, $salt)
72+
public function getCustomUserURL(string $username, int $userId, string $salt): string
12373
{
12474
$hash = password_hash($salt.$userId, PASSWORD_BCRYPT);
75+
12576
if (false === $hash) {
126-
throw new Exception('hash generation failed');
77+
throw new Exception('Hash generation failed');
12778
}
12879

12980
return sprintf(
@@ -140,21 +91,16 @@ public function getCustomUserURL($username, $userId, $salt)
14091
}
14192

14293
/**
143-
* Returns a user-specific URL, with two extra query string parameters : 'uid' and 'hash'.
144-
* 'hash' is generated using $salt and $userId.
145-
*
146-
* @param string $userId the user identifier, to build the hash and to include for the uid parameter
147-
* @param string $salt the salt, to build the hash
94+
* Returns a user-specific URL with uid and hash parameters.
14895
*
149-
* @throws Exception on hash generation failure
150-
*
151-
* @return string the custom user redirect URL
96+
* @throws Exception
15297
*/
153-
public function getCustomUserRedirectURL($userId, $salt)
98+
public function getCustomUserRedirectURL(int $userId, string $salt): string
15499
{
155100
$hash = password_hash($salt.$userId, PASSWORD_BCRYPT);
101+
156102
if (false === $hash) {
157-
throw new Exception('hash generation failed');
103+
throw new Exception('Hash generation failed');
158104
}
159105

160106
return sprintf(
Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,44 @@
11
User Remote Service Plugin
22
==========================
33

4-
Appends site-specific iframe-targeted user-identifying links to the menu bar.
5-
You can also hide the link from the menu and use the redirect link to use it as a link in a course.
4+
This plugin exposes signed user-specific links to external services.
65

7-
After activation, configure a __salt__ then select region __menu_administrator__.
6+
Flow
7+
----
88

9-
In the __Administration__ menu, in block __Plugins__,
10-
you will find a link __User Remote Services__ to manage the services in the main menu (title and URL) and also get the redirect URL to use in a course.
9+
1. Enable the plugin from the plugin list.
10+
2. Configure the plugin settings:
11+
- Salt: required to generate signed hashes.
12+
- Hide links from navigation menu: optional.
13+
3. Open the plugin administration page and create remote services with HTTP/HTTPS URLs.
14+
4. Authenticated users can open the service through:
15+
- iframe.php?serviceId=ID
16+
- redirect.php?serviceId=ID
17+
18+
Generated parameters
19+
--------------------
20+
21+
Iframe URLs receive:
22+
23+
- username
24+
- hash
25+
26+
Redirect URLs receive:
27+
28+
- uid
29+
- hash
30+
31+
The remote service can verify the hash with:
32+
33+
```php
34+
password_verify($salt.$userId, $hash)
35+
```
36+
37+
Security notes
38+
--------------
39+
40+
- Only authenticated Chamilo users can open service links.
41+
- Service URLs must use HTTP or HTTPS.
42+
- Empty or invalid service IDs are rejected.
43+
- The plugin does not create or update Chamilo users.
44+
- The plugin only signs the current authenticated user and redirects/embeds the configured service.

public/plugin/UserRemoteService/admin.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@
66
api_protect_admin_script(true);
77

88
$plugin = UserRemoteServicePlugin::create();
9+
$plugin->handleAdminPost();
910

1011
Display::display_header($plugin->get_title());
1112

12-
echo $plugin->getCreationForm()->returnForm();
13-
14-
echo $plugin->getDeletionForm()->returnForm();
15-
16-
echo $plugin->getServiceHTMLTable();
13+
echo $plugin->getAdminPageHtml();
1714

1815
Display::display_footer();

public/plugin/UserRemoteService/iframe.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33

44
require_once __DIR__.'/config.php';
55

6-
if (!api_user_is_login()) {
6+
if (api_is_anonymous()) {
77
api_not_allowed(true);
88
}
99

1010
$plugin = UserRemoteServicePlugin::create();
1111

12-
Display::display_header();
12+
if (!$plugin->isEnabled()) {
13+
api_not_allowed(true);
14+
}
15+
16+
Display::display_header($plugin->get_title());
1317

1418
echo $plugin->getIFrame();
1519

public/plugin/UserRemoteService/index.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33

44
require_once __DIR__.'/config.php';
55

6-
if ('true' !== UserRemoteServicePlugin::create()->get_hide_link_from_navigation_menu()) {
7-
foreach (UserRemoteServicePlugin::create()->getNavigationMenu() as $key => $menu) {
8-
$template->params['menu'][$key] = $menu;
9-
}
6+
$plugin = UserRemoteServicePlugin::create();
7+
8+
if (!api_user_is_login() || !$plugin->isEnabled() || $plugin->get_hide_link_from_navigation_menu()) {
9+
return;
10+
}
11+
12+
foreach ($plugin->getNavigationMenu() as $key => $menu) {
13+
$template->params['menu'][$key] = $menu;
1014
}

public/plugin/UserRemoteService/lang/en_US.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,19 @@
2222
$strings['ServiceTitle'] = 'Service title';
2323
$strings['ServiceURL'] = 'Service web site location (URL)';
2424
$strings['RedirectAccessURL'] = "URL to use in Chamilo to redirect user to the service (URL)";
25+
$strings['Actions'] = 'Actions';
26+
$strings['AddRemoteService'] = 'Add remote service';
27+
$strings['CurrentServices'] = 'Current services';
28+
$strings['DeleteService'] = 'Delete service';
29+
$strings['InvalidSecurityToken'] = 'Invalid security token.';
30+
$strings['InvalidServiceTitle'] = 'Please enter a service title.';
31+
$strings['InvalidServiceUrl'] = 'Please enter a valid HTTP or HTTPS URL.';
32+
$strings['MissingSaltWarning'] = 'Configure a salt before exposing remote service links. The salt is required to generate signed user URLs.';
33+
$strings['NoServicesConfigured'] = 'No remote services have been configured yet.';
34+
$strings['OpenInIframe'] = 'Open in iframe';
35+
$strings['OpenRedirect'] = 'Open redirect URL';
36+
$strings['RemoteServicesDescription'] = 'Manage external services that receive signed user URLs from Chamilo. Only authenticated users can open these links.';
37+
$strings['ServiceCreated'] = 'The remote service has been created.';
38+
$strings['ServiceDeleted'] = 'The remote service has been deleted.';
39+
$strings['ServiceManagement'] = 'Remote service management';
40+
$strings['ServiceUnavailable'] = 'This remote service is not available. Check that the plugin is enabled, the salt is configured and the URL is valid.';

public/plugin/UserRemoteService/lang/es.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,19 @@
2121
$strings['ServiceTitle'] = 'Título del servicio';
2222
$strings['ServiceURL'] = 'Ubicación del sitio web del servicio (URL)';
2323
$strings['RedirectAccessURL'] = 'URL a usar en Chamilo para redirigir al usuario al servicio (URL)';
24+
$strings['Actions'] = 'Acciones';
25+
$strings['AddRemoteService'] = 'Añadir servicio remoto';
26+
$strings['CurrentServices'] = 'Servicios actuales';
27+
$strings['DeleteService'] = 'Eliminar servicio';
28+
$strings['InvalidSecurityToken'] = 'Token de seguridad inválido.';
29+
$strings['InvalidServiceTitle'] = 'Ingrese un título para el servicio.';
30+
$strings['InvalidServiceUrl'] = 'Ingrese una URL HTTP o HTTPS válida.';
31+
$strings['MissingSaltWarning'] = 'Configure una sal antes de exponer enlaces de servicios remotos. La sal es necesaria para generar URLs firmadas de usuario.';
32+
$strings['NoServicesConfigured'] = 'Todavía no hay servicios remotos configurados.';
33+
$strings['OpenInIframe'] = 'Abrir en iframe';
34+
$strings['OpenRedirect'] = 'Abrir URL de redirección';
35+
$strings['RemoteServicesDescription'] = 'Administra servicios externos que reciben URLs firmadas de usuario desde Chamilo. Solo usuarios autenticados pueden abrir estos enlaces.';
36+
$strings['ServiceCreated'] = 'El servicio remoto ha sido creado.';
37+
$strings['ServiceDeleted'] = 'El servicio remoto ha sido eliminado.';
38+
$strings['ServiceManagement'] = 'Gestión de servicios remotos';
39+
$strings['ServiceUnavailable'] = 'Este servicio remoto no está disponible. Verifique que el plugin esté activo, la sal esté configurada y la URL sea válida.';

public/plugin/UserRemoteService/redirect.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,21 @@
33

44
require_once __DIR__.'/config.php';
55

6-
if (!api_user_is_login()) {
6+
if (api_is_anonymous()) {
77
api_not_allowed(true);
88
}
99

1010
$plugin = UserRemoteServicePlugin::create();
1111

12-
header('Location: '.$plugin->getActiveServiceSpecificUserUrl());
12+
if (!$plugin->isEnabled()) {
13+
api_not_allowed(true);
14+
}
15+
16+
$url = $plugin->getActiveServiceSpecificUserUrl();
17+
18+
if (empty($url)) {
19+
api_not_allowed(true);
20+
}
21+
22+
header('Location: '.$url);
1323
exit;

0 commit comments

Comments
 (0)