Project

General

Profile

Patch #39181 » 0001-API-compatibility-to-legacy-status-and-name-query-pa.patch

Jens Krämer, 2023-10-13 05:15

View differences:

app/controllers/users_controller.rb
45 45
    use_session = !request.format.csv?
46 46
    retrieve_query(UserQuery, use_session)
47 47

  
48
    # API backwards compatibility: handle legacy status and name filter parameters
49
    unless request.format.html?
50
      if status_id = params[:status].presence
51
        @query.add_filter 'status', '=', [status_id]
52
      end
53
      if name = params[:name].presence
54
        @query.add_filter 'name', '~', [name]
55
      end
56
      if group_id = params[:group_id].presence
57
        @query.add_filter 'is_member_of_group', '=', [group_id]
58
      end
59
    end
60

  
48 61
    if @query.valid?
49 62
      scope = @query.results_scope
50 63

  
app/models/user_query.rb
51 51
        type: :list_optional,
52 52
        values: ->{ Redmine::Twofa.available_schemes.map {|s| [I18n.t("twofa__#{s}__name"), s] } }
53 53
    end
54
    add_available_filter "name", type: :text, label: :field_name_or_email_or_login
54 55
    add_available_filter "login", type: :string
55 56
    add_available_filter "firstname", type: :string
56 57
    add_available_filter "lastname", type: :string
......
165 166

  
166 167
    joins.any? ? joins.join(' ') : nil
167 168
  end
169

  
170
  def sql_for_name_field(field, operator, value)
171
    case operator
172
    when '*'
173
      '1=1'
174
    when '!*'
175
      '1=0'
176
    else
177
      # match = (operator == '~')
178
      match = !operator.start_with?('!')
179
      matching_operator = operator.sub /^\!/, ''
180
      name_sql = %w(login firstname lastname).map{|field| sql_for_field(:name, operator, value, User.table_name, field)}
181

  
182
      emails = EmailAddress.table_name
183
      email_sql = <<-SQL
184
        #{match ? "EXISTS" : "NOT EXISTS"}
185
        (SELECT 1 FROM #{emails} WHERE
186
          #{emails}.user_id = #{User.table_name}.id AND
187
          #{sql_for_field(:name, matching_operator, value, emails, 'address')})
188
      SQL
189

  
190
      conditions = name_sql + [email_sql]
191
      op = match ? " OR " : " AND "
192
      "(#{conditions.map{|s| "(#{s})"}.join(op)})"
193
    end
194

  
195
  end
168 196
end
config/locales/en.yml
1410 1410
  twofa_text_group_disabled: "This setting is only effective when the global two factor authentication setting is set to 'optional'. Currently, two factor authentication is disabled."
1411 1411
  text_user_destroy_confirmation: "Are you sure you want to delete this user and remove all references to them? This cannot be undone. Often, locking a user instead of deleting them is the better solution. To confirm, please enter their login (%{login}) below."
1412 1412
  text_project_destroy_enter_identifier: "To confirm, please enter the project's identifier (%{identifier}) below."
1413
  field_name_or_email_or_login: Name, email or login
test/integration/api_test/users_test.rb
82 82
    end
83 83
  end
84 84

  
85
  test "GET /users.json with legacy filter params" do
86
    get '/users.json', :headers => credentials('admin'), params: { status: 3 }
87
    assert_response :success
88
    json = ActiveSupport::JSON.decode(response.body)
89
    assert json.key?('users')
90
    users = User.where(status: 3)
91
    assert_equal users.size, json['users'].size
92

  
93
    get '/users.json', :headers => credentials('admin'), params: { name: 'jsmith' }
94
    assert_response :success
95
    json = ActiveSupport::JSON.decode(response.body)
96
    assert json.key?('users')
97
    assert_equal 1, json['users'].size
98
    assert_equal 2, json['users'][0]['id']
99

  
100
    get '/users.json', :headers => credentials('admin'), params: { group_id: '10' }
101
    assert_response :success
102
    json = ActiveSupport::JSON.decode(response.body)
103
    assert json.key?('users')
104
    assert_equal 1, json['users'].size
105
    assert_equal 8, json['users'][0]['id']
106

  
107
    # there should be an implicit filter for status = 1
108
    User.where(id: [2, 8]).update_all status: 3
109

  
110
    get '/users.json', :headers => credentials('admin'), params: { name: 'jsmith' }
111
    assert_response :success
112
    json = ActiveSupport::JSON.decode(response.body)
113
    assert json.key?('users')
114
    assert_equal 0, json['users'].size
115

  
116
    get '/users.json', :headers => credentials('admin'), params: { group_id: '10' }
117
    assert_response :success
118
    json = ActiveSupport::JSON.decode(response.body)
119
    assert json.key?('users')
120
    assert_equal 0, json['users'].size
121
  end
122

  
85 123
  test "GET /users/:id.xml should return the user" do
86 124
    Redmine::Configuration.with 'avatar_server_url' => 'https://gravatar.com' do
87 125
      with_settings :gravatar_enabled => '1', :gravatar_default => 'robohash' do
test/unit/user_query_test.rb
108 108
    end
109 109
  end
110 110

  
111
  def test_name_or_email_or_login_filter
112
    [
113
      ['~', 'jsmith', [2]],
114
      ['^', 'jsm', [2]],
115
      ['$', 'ith', [2]],
116
      ['~', 'john', [2]],
117
      ['~', 'smith', [2]],
118
      ['~', 'somenet', [1, 2, 3, 4]],
119
      ['!~', 'somenet', [7, 8, 9]],
120
      ['^', 'dlop', [3]],
121
      ['$', 'bar', [7, 8, 9]],
122
      ['=', 'bar', []],
123
      ['=', 'someone@foo.bar', [7]],
124
      ['*', '', [1, 2, 3, 4, 7, 8, 9]],
125
      ['!*', '', []],
126
    ].each do |op, string, result|
127
      q = UserQuery.new name: '_'
128
      q.add_filter('name', op, [string])
129
      users = find_users_with_query q
130
      assert_equal result, users.map(&:id).sort, "#{op} #{string} should have found #{result}"
131
    end
132
  end
133

  
111 134
  def test_group_filter
112 135
    q = UserQuery.new name: '_'
113 136
    q.add_filter('is_member_of_group', '=', ['10', '99'])
(2-2/2)