redmica / redmine_ip_filter

3 stars 3 forks source link

[New Feature] IP Filtering per User #41

Open SinghNanak opened 1 year ago

SinghNanak commented 1 year ago

The plugin allows administrators to create and manage whitelists and blacklists for each user, granting or denying login access based on their IP address. By employing IP restrictions, organizations can mitigate unauthorized access, enforce location-based security policies, and safeguard sensitive data. With the new feature that allows user-specific IP filtering rules, the plugin becomes even more flexible and powerful. Administrators can now set different IP filtering rules for each user, giving them granular control over who can access their Redmine instance and from where.

SinghNanak commented 1 year ago

Dear @vividtone and @takenory ,

I hope this message finds you well. I am writing to you as a user of the redmine_ip_filter plugin and I wanted to share with you a feature that I believe would greatly benefit the plugin.

Currently, the plugin allows for IP filtering at the global level, but I would like to suggest the addition of a feature that allows for IP filtering at the user level. This would be incredibly useful for those of us who need to restrict access to certain users based on their IP address.

As a non-expert in Redmine plugin development, I understand that this may not be a simple task, but I would like to offer my assistance in any way that I can. I have some code snippets that I believe could be useful in the addition of this feature and I would be happy to share them with you.

I am reaching out to you because I believe that you have the knowledge and expertise to bring this feature to life. Without this feature, I will unfortunately have to look for an alternative solution to Redmine, which I would prefer not to do.

Thank you for your time and consideration. I look forward to your response

SinghNanak commented 1 year ago

/db/migrate/001_add_user_ip_address_filter.rb

class CreateIpFilters < ActiveRecord::Migration[6.0]
  def change
    create_table :ip_filters do |t|
      t.integer :user_id, null: false
      t.string :allowed_ips
      t.timestamps
    end
  end
end

/app/models/user_ip_filter.rb

class UserIpFilter < ApplicationRecord
  belongs_to :user

  validates :user, presence: true
  validate :validate_allowed_ips

  private

  def validate_allowed_ips
    if allowed_ips.present?
      allowed_ips.split.each do |ip|
        unless valid_ip_address?(ip)
          errors.add(:allowed_ips, :invalid_ip_address, message: "is not a valid IP address")
        end
      end
    end
  end

  def valid_ip_address?(ip)
    IPAddr.new(ip)
    true
  rescue IPAddr::InvalidAddressError
    false
  end
end

/app/models/user.rb

class User < ApplicationRecord
  has_one :user_ip_filter, dependent: :destroy

  def allowed_ip_address?(ip)
    if user_ip_filter
      user_ip_filter.allowed_ips.split.any? do |range|
        IPAddr.new(range).include?(ip)
      end
    else
      true
    end
  end
end

/lib/redmine_ip_filter/application_controller_patch.rb

module RedmineIpFilter
  module ApplicationControllerPatch
    def self.included(base)
      base.send(:include, InstanceMethod)
      base.class_eval do
        before_action :check_remote_ip
      end
    end

    module InstanceMethod
      def check_remote_ip
        unless FilterRule.valid_access?(request.remote_ip) || (User.current.allowed_ip_address?(request.remote_ip) if User.current.present?)
          @project = nil
          @message = l(:notice_forbidden_access_from_your_ip, :ip => request.remote_ip)
          respond_to do |format|
            format.html {
              render :template => 'filter_rules/403', :layout => false, :status => 403
            }
            format.any { head 403 }
          end
          logger.info "redmine_ip_filter: rejected access from #{request.remote_ip} " \
            "(HTTP_CLIENT_IP=#{request.client_ip.inspect} " \
            "HTTP_X_FORWARDED_FOR=#{request.x_forwarded_for.inspect})"
          return false
        end
      end
    end
  end
end

/app/controllers/user_ip_filters_controller.rb

class UserIpFiltersController < ApplicationController
  before_action :find_user
  before_action :find_user_ip_filter, only: [:edit, :update, :destroy]

  def new
    @user_ip_filter = UserIpFilter.new(user: @user)
  end

  def create
    @user_ip_filter = UserIpFilter.new(user_ip_filter_params)
    @user_ip_filter.user = @user

    if @user_ip_filter.save
      redirect_to user_path(@user), notice: 'User IP filter was successfully created.'
    else
      render :new
    end
  end

  def edit
  end

  def update
    if @user_ip_filter.update(user_ip_filter_params)
      redirect_to user_path(@user), notice: 'User IP filter was successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    @user_ip_filter.destroy
    redirect_to user_path(@user), notice: 'User IP filter was successfully deleted.'
  end

  private

  def find_user
    @user = User.find(params[:user_id])
  end

  def find_user_ip_filter
    @user_ip_filter = UserIpFilter.find(params[:id])
  end

  def user_ip_filter_params
    params.require(:user_ip_filter).permit(:allowed_ips)
  end
end
SinghNanak commented 1 year ago

you could also add option for "ALL" / "Everyone" that would apply filtering rule to everyone. This would keep the current functionality

vividtone commented 1 year ago

Thank you for suggesting a new feature. However, we have no plans at this time to implement the feature for the following reasons: