CVE-2022-4719: Insufficient Information (NVD-CWE-noinfo)
value: Business Logic Errors in GitHub repository ikus060/rdiffweb prior to 2.5.5.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2022-12-27T15:15Z
Repository: https://github.com/ikus060/rdiffweb
Language: Python
Files changed: README.md, rdiffweb/core/model/_user.py, rdiffweb/core/notification.py, rdiffweb/core/tests/test_notification.py, rdiffweb/templates/email_authorizedkey_added.html
Commit date: 2022-12-23 14:02:43 -0500
Commit message: Send notification on new SSH Key

Diff: diff --git a/README.md b/README.md
index 8fdc45e3e..e5a4ae850 100644
--- a/README.md
+++ b/README.md
@@ -106,11 +106,17 @@ Bug reports should be reported on the Rdiffweb Gitlab at https://gitlab.com/ikus
 
 Professional support for Rdiffweb is available by contacting [IKUS Soft](https://www.ikus-soft.com/en/support/#form).
 
 # Changelog
 
-## Next Release - 2.5.4
+## Next Release - 2.5.5
+
+* Fix loading of Charts in Status page
+* Ensure Gmail and other mail client doesn't create hyperlink automatically for any nodification sent by Rdiffweb to avoid phishing - credit to [Nehal Pillai](https://www.linkedin.com/in/nehal-pillai-02a854172)
+* Sent email notification to user when a new SSH Key get added - credit to [Nehal Pillai](https://www.linkedin.com/in/nehal-pillai-02a854172)
+
+## 2.5.4 (2022-12-19)
 
 * Discard `X-Forwarded-Host` headers credit to [Anishka Shukla](https://github.com/anishkashukla)
 * Create proper symbolic link of `chartkick.js` on Ubuntu Jammy to fix loading of Charts in web interface
 * Add CSRF verification on `/logout` credits to [reza.duty](https://rezaduty.me)
 
diff --git a/rdiffweb/core/model/_user.py b/rdiffweb/core/model/_user.py
index 9a9d9dd8d..49cf0893d 100644
--- a/rdiffweb/core/model/_user.py
+++ b/rdiffweb/core/model/_user.py
@@ -180,10 +180,11 @@ class UserObject(Base):
             except IntegrityError:
                 raise DuplicateSSHKeyError(
                     _("Duplicate key. This key already exists or is associated to another user.")
                 )
         cherrypy.engine.publish('user_attr_changed', self, {'authorizedkeys': True})
+        cherrypy.engine.publish('authorizedkey_added', self, fingerprint=key.fingerprint, comment=comment)
 
     def add_access_token(self, name, expiration_time=None, length=16):
         """
         Create a new access token. Return the un-encrypted value of the token.
         """
diff --git a/rdiffweb/core/notification.py b/rdiffweb/core/notification.py
index 503533936..053ea36e3 100644
--- a/rdiffweb/core/notification.py
+++ b/rdiffweb/core/notification.py
@@ -43,19 +43,21 @@ class NotificationPlugin(SimplePlugin):
 
     def start(self):
         self.bus.log('Start Notification plugin')
         self.bus.publish('schedule_job', self.execution_time, self.notification_job)
         self.bus.subscribe('access_token_added', self.access_token_added)
+        self.bus.subscribe('authorizedkey_added', self.authorizedkey_added)
         self.bus.subscribe('user_attr_changed', self.user_attr_changed)
         self.bus.subscribe('user_password_changed', self.user_password_changed)
 
     start.priority = 55
 
     def stop(self):
         self.bus.log('Stop Notification plugin')
         self.bus.publish('unschedule_job', self.notification_job)
         self.bus.unsubscribe('access_token_added', self.access_token_added)
+        self.bus.unsubscribe('authorizedkey_added', self.authorizedkey_added)
         self.bus.unsubscribe('user_attr_changed', self.user_attr_changed)
         self.bus.unsubscribe('user_password_changed', self.user_password_changed)
 
     stop.priority = 45
 
@@ -75,10 +77,25 @@ class NotificationPlugin(SimplePlugin):
         body = self.app.templates.compile_template(
             "email_access_token_added.html", **{"header_name": self.app.cfg.header_name, 'user': userobj, 'name': name}
         )
         self.bus.publish('queue_mail', to=userobj.email, subject=_("A new access token has been created"), message=body)
 
+    def authorizedkey_added(self, userobj, fingerprint, comment, **kwargs):
+        if not self.send_changed:
+            return
+
+        if not userobj.email:
+            logger.info("can't sent mail to user [%s] without an email", userobj.username)
+            return
+
+        # If the email attributes was changed, send a mail notification.
+        body = self.app.templates.compile_template(
+            "email_authorizedkey_added.html",
+            **{"header_name": self.app.cfg.header_name, 'user': userobj, 'comment': comment, 'fingerprint': fingerprint}
+        )
+        self.bus.publish('queue_mail', to=userobj.email, subject=_("A new SSH Key has been added"), message=body)
+
     def user_attr_changed(self, userobj, attrs={}):
         if not self.send_changed:
             return
 
         # Leave if the mail was not changed.
diff --git a/rdiffweb/core/tests/test_notification.py b/rdiffweb/core/tests/test_notification.py
index ddfae0cf5..5bcb1a67d 100644
--- a/rdiffweb/core/tests/test_notification.py
+++ b/rdiffweb/core/tests/test_notification.py
@@ -186,5 +186,44 @@ class NotificationPluginTest(rdiffweb.test.WebCase):
         self.listener.queue_email.assert_called_once_with(
             to='password_change@test.com',
             subject='Password changed',
             message='<html>\n  <head></head>\n  <body>\n    <p>\n      <a>Hey admin,</a>\n    </p>\n    <p>You recently changed the password associated with your Rdiffweb account.</p>\n    <p>\n      If you did not make this change and believe your account has been compromised, please contact your administrator.\n    </p>\n  </body>\n</html>',
         )
+
+    def test_access_token_added(self):
+        # Given a user with a email.
+        user = UserObject.get_user(self.USERNAME)
+        user.email = 'password_change@test.com'
+        user.set_password('new_password')
+        user.add().commit()
+        self.listener.queue_email.reset_mock()
+
+        # When adding a new access token
+        user.add_access_token('TEST')
+
+        # Then a notification is sent to the user
+        self.listener.queue_email.assert_called_once_with(
+            to='password_change@test.com',
+            subject='A new access token has been created',
+            message='<html>\n  <head></head>\n  <body>\n    <p>\n      <a>Hey admin,</a>\n    </p>\n    <p>\n      <a>A new access token, named "TEST", has been created.</a>\n    </p>\n    <p>\n      If you did not make this change and believe your account has been compromised, please contact your administrator.\n    </p>\n  </body>\n</html>',
+        )
+
+    def test_authorizedkey_added(self):
+        # Given a user with a email.
+        user = UserObject.get_user(self.USERNAME)
+        user.email = 'password_change@test.com'
+        user.set_password('new_password')
+        user.add().commit()
+        self.listener.queue_email.reset_mock()
+
+        # When adding a new access token
+        user.add_authorizedkey(
+            key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSEN5VTn9MLituZvdYTZMbZEaMxe0UuU7BelxHkvxzSpVWtazrIBEc3KZjtVoK9F3+0kd26P4DzSQuPUl3yZDgyZZeXrF6p2GlEA7A3tPuOEsAQ9c0oTiDYktq5/Go8vD+XAZKLd//qmCWW1Jg4datkWchMKJzbHUgBrBH015FDbGvGDWYTfVyb8I9H+LQ0GmbTHsuTu63DhPODncMtWPuS9be/flb4EEojMIx5Vce0SNO9Eih38W7jTvNWxZb75k5yfPJxBULRnS5v/fPnDVVtD3JSGybSwKoMdsMX5iImAeNhqnvd8gBu1f0IycUQexTbJXk1rPiRcF13SjKrfXz ikus060@ikus060-t530",
+            comment="test@mysshkey",
+        )
+
+        # Then a notification is sent to the user
+        self.listener.queue_email.assert_called_once_with(
+            to='password_change@test.com',
+            subject='A new SSH Key has been added',
+            message='<html>\n  <head></head>\n  <body>\n    <p>\n      <a>Hey admin,</a>\n    </p>\n    <p>\n      <a>A new SSH Key, titled "test@mysshkey" with fingerprint "4d:42:8b:35:e5:55:71:f7:b3:0d:58:f9:b1:2c:9e:91" has been created in your account.</a>\n    </p>\n    <p>\n      If you did not make this change and believe your account has been compromised, please contact your administrator.\n    </p>\n  </body>\n</html>',
+        )
diff --git a/rdiffweb/templates/email_authorizedkey_added.html b/rdiffweb/templates/email_authorizedkey_added.html
new file mode 100644
index 000000000..dcf5fbd56
--- /dev/null
+++ b/rdiffweb/templates/email_authorizedkey_added.html
@@ -0,0 +1,14 @@
+<html>
+  <head></head>
+  <body>
+    <p>
+      <a>{% trans username=(user.fullname or user.username) %}Hey {{ username }},{% endtrans %}</a>
+    </p>
+    <p>
+      <a>{% trans %}A new SSH Key, titled "{{ comment }}" with fingerprint "{{ fingerprint }}" has been created in your account.{% endtrans %}</a>
+    </p>
+    <p>
+      {% trans %}If you did not make this change and believe your account has been compromised, please contact your administrator.{% endtrans %}
+    </p>
+  </body>
+</html>

Vulnerable code: ## Next Release - 2.5.4
Fixed code: ## Next Release - 2.5.5

* Fix loading of Charts in Status page
* Ensure Gmail and other mail client doesn't create hyperlink automatically for any nodification sent by Rdiffweb to avoid phishing - credit to [Nehal Pillai](https://www.linkedin.com/in/nehal-pillai-02a854172)
* Sent email notification to user when a new SSH Key get added - credit to [Nehal Pillai](https://www.linkedin.com/in/nehal-pillai-02a854172)

## 2.5.4 (2022-12-19)
        cherrypy.engine.publish('authorizedkey_added', self, fingerprint=key.fingerprint, comment=comment)
        self.bus.subscribe('authorizedkey_added', self.authorizedkey_added)
        self.bus.unsubscribe('authorizedkey_added', self.authorizedkey_added)
    def authorizedkey_added(self, userobj, fingerprint, comment, **kwargs):
        if not self.send_changed:
            return

        if not userobj.email:
            logger.info("can't sent mail to user [%s] without an email", userobj.username)
            return

        # If the email attributes was changed, send a mail notification.
        body = self.app.templates.compile_template(
            "email_authorizedkey_added.html",
            **{"header_name": self.app.cfg.header_name, 'user': userobj, 'comment': comment, 'fingerprint': fingerprint}
        )
        self.bus.publish('queue_mail', to=userobj.email, subject=_("A new SSH Key has been added"), message=body)


    def test_access_token_added(self):
        # Given a user with a email.
        user = UserObject.get_user(self.USERNAME)
        user.email = 'password_change@test.com'
        user.set_password('new_password')
        user.add().commit()
        self.listener.queue_email.reset_mock()

        # When adding a new access token
        user.add_access_token('TEST')

        # Then a notification is sent to the user
        self.listener.queue_email.assert_called_once_with(
            to='password_change@test.com',
            subject='A new access token has been created',
            message='<html>\n  <head></head>\n  <body>\n    <p>\n      <a>Hey admin,</a>\n    </p>\n    <p>\n      <a>A new access token, named "TEST", has been created.</a>\n    </p>\n    <p>\n      If you did not make this change and believe your account has been compromised, please contact your administrator.\n    </p>\n  </body>\n</html>',
        )

    def test_authorizedkey_added(self):
        # Given a user with a email.
        user = UserObject.get_user(self.USERNAME)
        user.email = 'password_change@test.com'
        user.set_password('new_password')
        user.add().commit()
        self.listener.queue_email.reset_mock()

        # When adding a new access token
        user.add_authorizedkey(
            key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSEN5VTn9MLituZvdYTZMbZEaMxe0UuU7BelxHkvxzSpVWtazrIBEc3KZjtVoK9F3+0kd26P4DzSQuPUl3yZDgyZZeXrF6p2GlEA7A3tPuOEsAQ9c0oTiDYktq5/Go8vD+XAZKLd//qmCWW1Jg4datkWchMKJzbHUgBrBH015FDbGvGDWYTfVyb8I9H+LQ0GmbTHsuTu63DhPODncMtWPuS9be/flb4EEojMIx5Vce0SNO9Eih38W7jTvNWxZb75k5yfPJxBULRnS5v/fPnDVVtD3JSGybSwKoMdsMX5iImAeNhqnvd8gBu1f0IycUQexTbJXk1rPiRcF13SjKrfXz ikus060@ikus060-t530",
            comment="test@mysshkey",
        )

        # Then a notification is sent to the user
        self.listener.queue_email.assert_called_once_with(
            to='password_change@test.com',
            subject='A new SSH Key has been added',
            message='<html>\n  <head></head>\n  <body>\n    <p>\n      <a>Hey admin,</a>\n    </p>\n    <p>\n      <a>A new SSH Key, titled "test@mysshkey" with fingerprint "4d:42:8b:35:e5:55:71:f7:b3:0d:58:f9:b1:2c:9e:91" has been created in your account.</a>\n    </p>\n    <p>\n      If you did not make this change and believe your account has been compromised, please contact your administrator.\n    </p>\n  </body>\n</html>',
        )
<html>
  <head></head>
  <body>
    <p>
      <a>{% trans username=(user.fullname or user.username) %}Hey {{ username }},{% endtrans %}</a>
    </p>
    <p>
      <a>{% trans %}A new SSH Key, titled "{{ comment }}" with fingerprint "{{ fingerprint }}" has been created in your account.{% endtrans %}</a>
    </p>
    <p>
      {% trans %}If you did not make this change and believe your account has been compromised, please contact your administrator.{% endtrans %}
    </p>
  </body>
</html>