CVE-2021-25991 (published 2021-12-29T09:15Z)

In Ifme, versions v5.0.0 to v7.32 are vulnerable against an improper access control, which makes it possible for admins to ban themselves leading to their deactivation from Ifme account and complete loss of admin access to Ifme.

Severity: MEDIUM
CVSS v2 base score: 4.9 (AV:N/AC:M/Au:S/C:N/I:P/A:P)
CVSS v3 base score: 7.3, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/ifmeorg/ifme
commit_details: repo_url: https://github.com/ifmeorg/ifme
author: Julia Nguyen
author_date: 2021-11-18 23:37:27-05:00
msg: [#2052] Fix improper access control leads to admin self-banning
repository: repo_url: https://github.com/ifmeorg/ifme
repo_name: ifmeorg/ifme
description: Free, open source mental health communication web app to share experiences with loved ones
repo_language: Ruby
owner: ifmeorg
file_changes: filename: notifications_controller.rb
change_type: ModificationType.MODIFY
diff: @@ -47,7 +47,9 @@ class NotificationsController < ApplicationController
     uniqueid = notification[:uniqueid]
     data = convert_to_hash(notification[:data])
     # In case data[:name] is invalid
-    data[:email] = User.find(data[:user_id]).email
+    if data[:user_id]
+      data[:email] = User.find(data[:user_id]).email
+    end
     case data[:type]
     when /comment/ then comment_link(uniqueid, data)
     when /accepted_ally_request/ then accepted_ally_link(uniqueid, data)

programming_language: Ruby
filename: comments_helper.rb
change_type: ModificationType.MODIFY
diff: @@ -42,6 +42,7 @@ module CommentsHelper
       commentByUid: user.uid,
       commentByName: user.name,
       commentByAvatar: user.avatar.url,
+      commentByAdmin: user.admin?,
       comment: sanitize(comment.comment),
       viewers: CommentViewersService.viewers(comment, current_user),
       createdAt: created_at(comment.created_at),

programming_language: Ruby
filename: index.html.erb
change_type: ModificationType.MODIFY
diff: @@ -56,9 +56,11 @@
           <div>
             <%= link_to t('common.actions.remove'), remove_allies_path(ally_id: ally.id), method: :post, data: { confirm: t('common.actions.confirm') } %>
           </div>
-          <div>
-            <%= link_to t('common.actions.report'), new_report_path(uid: ally.uid) %>
-          </div>
+          <% if !ally.admin? && ally != current_user %>
+            <div>
+              <%= link_to t('common.actions.report'), new_report_path(uid: ally.uid) %>
+            </div>
+          <% end %>
         </div>
       <% end %>
     </div>

programming_language: CoffeeScript
filename: index.html.erb
change_type: ModificationType.MODIFY
diff: @@ -26,9 +26,11 @@
         <div>
           <%= link_to t('common.actions.remove'), remove_allies_path(ally_id: @profile.id), method: :post, data: { confirm: t('common.actions.confirm') } %>
         </div>
-        <div>
-          <%= link_to t('common.actions.report'), new_report_path(uid: @profile.uid) %>
-        </div>
+        <% if !@profile.admin? && @profile != current_user %>
+          <div>
+            <%= link_to t('common.actions.report'), new_report_path(uid: @profile.uid) %>
+          </div>
+        <% end %>
       <% elsif current_user.allies_by_status(:pending_from_user).include? @profile %>
         <div>
           <%= link_to t('allies.accept'), add_allies_path(ally_id: @profile.id), method: :post %> | <%= link_to t('allies.reject'), remove_allies_path(ally_id: @profile.id), method: :post, data: { confirm: t('common.actions.confirm') } %>
@@ -39,7 +41,7 @@
         </div>
       <% end %>
     <% end %>
-    <% if current_user.admin? %>
+    <% if @profile != current_user && current_user.admin? %>
       <% if @profile.banned? %>
         <div>
           <%= link_to t('reports.remove_ban'), remove_ban_profile_index_path(user_id: @profile.id), method: :post %>

programming_language: Ruby
filename: index.html.erb
change_type: ModificationType.MODIFY
diff: @@ -15,9 +15,11 @@
             <div>
               <%= link_to t('common.actions.remove'), remove_allies_path(ally_id: user.id), method: :post, data: { confirm: t('common.actions.confirm') } %>
             </div>
-            <div>
-              <%= link_to t('common.actions.report'), new_report_path(uid: user.uid) %>
-            </div>
+            <% if !user.admin? && user != current_user %>
+              <div>
+                <%= link_to t('common.actions.report'), new_report_path(uid: user.uid) %>
+              </div>
+            <% end %>
           <% else %>
             <%= link_to t('allies.add_ally'), add_allies_path(ally_id: user.id), method: :post %>
           <% end %>

programming_language: Ruby
filename: Comments.spec.jsx
change_type: ModificationType.MODIFY
diff: @@ -64,50 +64,92 @@ const value = 'Hey';
 
 const id = 1;
 
-const comment = {
+const getComment = ({ commentByAdmin } = {}) => ({
   comment: value,
-  currentUserId: 'some-uid',
+  currentUserUid: 'some-uid',
   commentByAvatar: null,
+  commentByAdmin: Boolean(commentByAdmin),
   commentByName: 'Kind Human',
   commentByUid: 'uid',
   createdAt: 'Created less than a minute ago',
   deleteAction: '/comments/delete?comment_id=1',
   id,
   viewers: 'Visible only between you and Lane Kim',
-};
-
-const component = <Comments formProps={formProps} />;
+});
 
 describe('Comments', () => {
-  beforeEach(() => {
-    jest.spyOn(axios, 'post').mockResolvedValue({
-      data: { comment },
+  describe('when written by a non-admin user', () => {
+    it('renders correctly with a report link', () => {
+      render(<Comments formProps={formProps} comments={[getComment()]} />);
+      expect(screen.getByRole('textbox')).toBeInTheDocument();
+      expect(screen.getByRole('article')).toBeInTheDocument();
+      expect(
+        screen.getByRole('button', { name: 'Submit' }),
+      ).toBeInTheDocument();
+      expect(screen.getByRole('link', { name: 'Report' })).toBeInTheDocument();
     });
-    jest.spyOn(axios, 'delete').mockResolvedValue({
-      data: { id },
+
+    it('add and delete a comment', async () => {
+      jest.spyOn(axios, 'post').mockResolvedValue({
+        data: { comment: getComment() },
+      });
+      jest.spyOn(axios, 'delete').mockResolvedValue({
+        data: { id },
+      });
+      render(<Comments formProps={formProps} />);
+      expect(screen.queryByRole('article')).not.toBeInTheDocument();
+
+      userEvent.type(screen.getByRole('textbox'));
+      userEvent.selectOptions(screen.getByRole('combobox'), 'private');
+      userEvent.click(screen.getByRole('button', { name: 'Submit' }));
+
+      await waitFor(() => expect(screen.getByRole('article')).toBeInTheDocument());
+      expect(screen.getByRole('article')).toHaveTextContent('Hey');
+
+      userEvent.click(screen.getByRole('link', { name: 'Delete' }));
+
+      await waitFor(() => expect(screen.queryByRole('article')).not.toBeInTheDocument());
     });
   });
 
-  it('renders correctly', () => {
-    render(component);
-    expect(screen.getByRole('textbox')).toBeInTheDocument();
-    expect(screen.queryByRole('article')).not.toBeInTheDocument();
-    expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
-  });
+  describe('when written by an admin user', () => {
+    it('renders correctly without a report link', () => {
+      render(
+        <Comments
+          formProps={formProps}
+          comments={[getComment({ commentByAdmin: true })]}
+        />,
+      );
+      expect(screen.getByRole('textbox')).toBeInTheDocument();
+      expect(screen.getByRole('article')).toBeInTheDocument();
+      expect(
+        screen.getByRole('button', { name: 'Submit' }),
+      ).toBeInTheDocument();
+      expect(
+        screen.queryByRole('link', { name: 'Report' }),
+      ).not.toBeInTheDocument();
+    });
 
-  it('add and delete a comment', async () => {
-    render(component);
-    expect(screen.queryByRole('article')).not.toBeInTheDocument();
+    it('add and delete a comment', async () => {
+      jest.spyOn(axios, 'post').mockResolvedValue({
+        data: { comment: getComment({ commentByAdmin: true }) },
+      });
+      jest.spyOn(axios, 'delete').mockResolvedValue({
+        data: { id },
+      });
+      render(<Comments formProps={formProps} />);
+      expect(screen.queryByRole('article')).not.toBeInTheDocument();
 
-    userEvent.type(screen.getByRole('textbox'));
-    userEvent.selectOptions(screen.getByRole('combobox'), 'private');
-    userEvent.click(screen.getByRole('button', { name: 'Submit' }));
+      userEvent.type(screen.getByRole('textbox'));
+      userEvent.selectOptions(screen.getByRole('combobox'), 'private');
+      userEvent.click(screen.getByRole('button', { name: 'Submit' }));
 
-    await waitFor(() => expect(screen.getByRole('article')).toBeInTheDocument());
-    expect(screen.getByRole('article')).toHaveTextContent('Hey');
+      await waitFor(() => expect(screen.getByRole('article')).toBeInTheDocument());
+      expect(screen.getByRole('article')).toHaveTextContent('Hey');
 
-    userEvent.click(screen.getByRole('link', { name: 'Delete' }));
+      userEvent.click(screen.getByRole('link', { name: 'Delete' }));
 
-    await waitFor(() => expect(screen.queryByRole('article')).not.toBeInTheDocument());
+      await waitFor(() => expect(screen.queryByRole('article')).not.toBeInTheDocument());
+    });
   });
 });

programming_language: TypeScript
filename: index.jsx
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,7 @@ type Comment = {
   commentByUid: string,
   commentByName: string,
   commentByAvatar?: string,
+  commentByAdmin: Boolean,
   comment: any,
   viewers?: string,
   createdAt: string,
@@ -68,9 +69,10 @@ export const Comments = ({ comments, formProps }: Props) => {
     currentUserUid: string,
     uid: string,
     id: number,
+    commentByAdmin: Boolean,
   ) => {
     const actions = {};
-    if (currentUserUid !== uid) {
+    if (currentUserUid !== uid && !commentByAdmin) {
       actions.report = reportAction(uid, id);
     }
     if (viewers) {
@@ -94,6 +96,7 @@ export const Comments = ({ comments, formProps }: Props) => {
       commentByUid,
       commentByName,
       commentByAvatar,
+      commentByAdmin,
       comment,
       viewers,
       createdAt,
@@ -113,6 +116,7 @@ export const Comments = ({ comments, formProps }: Props) => {
               currentUserUid,
               commentByUid,
               id,
+              commentByAdmin,
             )}
             hasStory
           />

programming_language: TypeScript
filename: comments_helper_spec.rb
change_type: ModificationType.MODIFY
diff: @@ -32,6 +32,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user1.uid,
                                                                                           commentByName: user1.name,
                                                                                           commentByAvatar: user1.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: nil,
@@ -47,6 +48,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user1.uid,
                                                                                           commentByName: user1.name,
                                                                                           commentByAvatar: user1.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: "Visible only between you and #{user2.name}",
@@ -68,6 +70,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user2.uid,
                                                                                           commentByName: user2.name,
                                                                                           commentByAvatar: user2.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: nil,
@@ -83,6 +86,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user2.uid,
                                                                                           commentByName: user2.name,
                                                                                           commentByAvatar: user2.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: "Visible only between you and #{user1.name}",
@@ -108,6 +112,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user1.uid,
                                                                                           commentByName: user1.name,
                                                                                           commentByAvatar: user1.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: nil,
@@ -123,6 +128,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user1.uid,
                                                                                           commentByName: user1.name,
                                                                                           commentByAvatar: user1.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: "Visible only between you and #{user2.name}",
@@ -144,6 +150,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user2.uid,
                                                                                           commentByName: user2.name,
                                                                                           commentByAvatar: user2.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: nil,
@@ -159,6 +166,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user2.uid,
                                                                                           commentByName: user2.name,
                                                                                           commentByAvatar: user2.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: "Visible only between you and #{user1.name}",
@@ -186,6 +194,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user1.uid,
                                                                                           commentByName: user1.name,
                                                                                           commentByAvatar: user1.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: nil,
@@ -204,6 +213,7 @@ describe CommentsHelper, type: :controller do
                                                                                           commentByUid: user2.uid,
                                                                                           commentByName: user2.name,
                                                                                           commentByAvatar: user2.avatar.url,
+                                                                                          commentByAdmin: false,
                                                                                           comment: comment,
                                                                                           createdAt: created_at,
                                                                                           viewers: nil,

programming_language: Ruby