diff --git a/app/controllers/api/encryptables_controller.rb b/app/controllers/api/encryptables_controller.rb index 371b7c3aa..bf8d8eaf5 100644 --- a/app/controllers/api/encryptables_controller.rb +++ b/app/controllers/api/encryptables_controller.rb @@ -72,6 +72,16 @@ def model_class end # rubocop:enable Metrics/MethodLength + def fetch_entries + if encryptable_file? + super + elsif tag_param.present? + user_encryptables.find_by(tag: tag_param) + else + Encryptables::FilteredList.new(current_user, params).fetch_entries + end + end + def build_entry return build_encryptable_file if encryptable_file? diff --git a/app/presenters/encryptables/filtered_list.rb b/app/presenters/encryptables/filtered_list.rb new file mode 100644 index 000000000..785dd0f47 --- /dev/null +++ b/app/presenters/encryptables/filtered_list.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module ::Encryptables + class FilteredList < ::FilteredList + + def fetch_entries + filtered_encryptables = encryptables + + filtered_encryptables = filter_by_recent if recent? + filtered_encryptables = filter_by_query(filtered_encryptables) if query + + + filtered_encryptables + end + + private + + def query + @params[:q]&.strip&.downcase + end + + def recent? + true?(@params[:recent]) + end + + def encryptables + @current_user.encryptables + end + + def filter_by_query(encryptables) + encryptables.where( + 'lower(encryptables.description) LIKE :query + OR lower(encryptables.name) LIKE :query', + query: "%#{query}%" + ) + end + + def filter_by_recent + Version + .includes(:encryptable, encryptable: [:folder]) + .where(whodunnit: @current_user) + .order(created_at: :desc) + .group(:item_id, :item_type) + .select(:item_id, :item_type) + .limit(5) + .map(&:encryptable) + end + end +end diff --git a/app/presenters/filtered_list.rb b/app/presenters/filtered_list.rb index 92b3a2b62..fb3dce893 100644 --- a/app/presenters/filtered_list.rb +++ b/app/presenters/filtered_list.rb @@ -15,4 +15,8 @@ def fetch_entries def list_param(key) @params[key].to_a.map(&:to_i) end + + def true?(value) + %w[1 yes true].include?(value.to_s.downcase) + end end diff --git a/frontend/app/routes/dashboard.js b/frontend/app/routes/dashboard.js index 3094f7e35..a09592dba 100644 --- a/frontend/app/routes/dashboard.js +++ b/frontend/app/routes/dashboard.js @@ -12,22 +12,31 @@ export default class DashboardRoute extends BaseRoute { }; async model(params) { - params["limit"] = 20; const favouriteTeams = await this.getFavouriteTeams(params); - const teams = this.getTeams(params); + const teams = await this.getTeams(params); + const recentCredentials = await this.getRecentCredentials(params); + return RSVP.hash({ favouriteTeams, - teams + teams, + recentCredentials }); } async getFavouriteTeams(params) { + params["limit"] = 20; params["favourite"] = true; return await this.store.query("team", params); } async getTeams(params) { + params["limit"] = 20; params["favourite"] = false; return await this.store.query("team", params); } + + async getRecentCredentials(params) { + params["recent"] = true; + return await this.store.query("encryptable", params); + } } diff --git a/frontend/app/templates/components/dashboard-card.hbs b/frontend/app/templates/components/dashboard-card.hbs index 1c13f91bc..be8342ea0 100644 --- a/frontend/app/templates/components/dashboard-card.hbs +++ b/frontend/app/templates/components/dashboard-card.hbs @@ -7,5 +7,5 @@ -

{{this.team.name}}

- \ No newline at end of file +

{{this.content.name}}

+ diff --git a/frontend/app/templates/dashboard.hbs b/frontend/app/templates/dashboard.hbs index 1046fa61a..bfc3e46c8 100644 --- a/frontend/app/templates/dashboard.hbs +++ b/frontend/app/templates/dashboard.hbs @@ -1,20 +1,32 @@
+ {{#if @model.recentCredentials}} +

Recent Credentials

+
+ {{#each this.model.recentCredentials as |encryptable|}} + + + + {{/each}} +
+ {{/if}} + {{#if @model.favouriteTeams}}

Favourites

{{#each this.model.favouriteTeams as |team|}} - + {{/each}}
{{/if}} + {{#if this.model.teams}}

Teams

{{#each this.model.teams as |team|}} - + {{/each}}
diff --git a/frontend/tests/integration/components/dashboard-card-test.js b/frontend/tests/integration/components/dashboard-card-test.js index 42a1806c1..316b4b05a 100644 --- a/frontend/tests/integration/components/dashboard-card-test.js +++ b/frontend/tests/integration/components/dashboard-card-test.js @@ -20,7 +20,7 @@ module("Integration | Component | dashboard-card", function (hooks) { // Template block usage: await render(hbs` - + `); diff --git a/spec/controllers/api/encryptables_controller_spec.rb b/spec/controllers/api/encryptables_controller_spec.rb index e551e166a..3122d15de 100644 --- a/spec/controllers/api/encryptables_controller_spec.rb +++ b/spec/controllers/api/encryptables_controller_spec.rb @@ -21,7 +21,8 @@ get :index, params: { 'q': 'Personal' }, xhr: true - credentials1_json = data.second + expect(data.count).to eq 1 + credentials1_json = data.first credentials1_json_attributes = credentials1_json['attributes'] credentials1_json_relationships = credentials1_json['relationships'] @@ -37,7 +38,7 @@ expect_json_object_includes_keys(credentials1_json_relationships, nested_models) end - it 'returns all enncryptables if empty query param given' do + it 'returns all encryptables if empty query param given' do login_as(:alice) get :index, params: { 'q': '' }, xhr: true @@ -129,6 +130,87 @@ expect(response.status).to eq(403) end + + context 'recent Credentials' do + let!(:recent_credentials) do + folder = teams(:team1).folders.first + private_key = decrypt_private_key(bob) + team_password = folder.team.decrypt_team_password(bob, private_key) + Fabricate.times( + 6, + :credential, + folder: folder, + team_password: team_password + ) + end + + it 'returns most recent credentials' do + login_as(:alice) + + recent_credentials.each do |credential| + log_read_access(alice.id, credential) + end + + get :index, params: { recent: true }, xhr: true + + expect(response.status).to be(200) + expect(data.size).to eq(5) + attributes = data.first['attributes'] + expect(attributes['name']).to eq recent_credentials.last.name + expect(attributes['description']).to eq recent_credentials.last.description + end + + it 'shows most recently used credential first in list' do + login_as(:alice) + + + recent_credentials.each do |credential| + log_read_access(alice.id, credential) + end + log_read_access(alice.id, credentials1) + + get :index, params: { recent: true }, xhr: true + + expect(response.status).to be(200) + expect(data.size).to eq(5) + attributes = data.first['attributes'] + expect(attributes['name']).to eq credentials1.name + expect(attributes['description']).to eq credentials1.description + + end + it 'does not show credentials with no access' do + login_as(:bob) + + recent_credentials1 = recent_credentials.first + log_read_access(alice.id, recent_credentials1) + + get :index, params: { recent: true }, xhr: true + + expect(response.status).to be(200) + expect(data.size).to eq(0) + end + + it 'does not show deleted credentials' do + login_as(:alice) + + recent_credentials1 = recent_credentials.first + log_read_access(alice.id, recent_credentials1) + + get :index, params: { recent: true }, xhr: true + + expect(data.size).to eq(1) + attributes = data.first['attributes'] + expect(attributes['name']).to eq recent_credentials1.name + expect(attributes['description']).to eq recent_credentials1.description + + recent_credentials1.destroy! + + get :index, params: { recent: true }, xhr: true + + expect(response.status).to be(200) + expect(data.size).to eq(0) + end + end end context 'GET show' do @@ -777,4 +859,12 @@ def set_auth_headers request.headers['Authorization-User'] = bob.username request.headers['Authorization-Password'] = Base64.encode64('password') end + + def log_read_access(user_id, credential) + v = credential.paper_trail.save_with_version + v.whodunnit = user_id + v.event = :viewed + v.created_at = DateTime.now + v.save! + end end diff --git a/spec/fabricators/encryptables/credentials_fabricator.rb b/spec/fabricators/encryptables/credentials_fabricator.rb index dd4cd5d7a..8797270d9 100644 --- a/spec/fabricators/encryptables/credentials_fabricator.rb +++ b/spec/fabricators/encryptables/credentials_fabricator.rb @@ -7,7 +7,7 @@ Fabricator(:credential, from: 'Encryptable::Credentials') do transient :team_password - name { Faker::Team.creature } + name { sequence(:name) { |i| Faker::Team.creature + " #{i}" } } cleartext_username { Faker::Internet.user_name } cleartext_password { Faker::Internet.password } before_save do |account, attrs| diff --git a/spec/system/dashboard_system_spec.rb b/spec/system/dashboard_system_spec.rb index 20d2e2182..44f9a987e 100644 --- a/spec/system/dashboard_system_spec.rb +++ b/spec/system/dashboard_system_spec.rb @@ -5,30 +5,55 @@ describe 'Dashboard', type: :system, js: true do include SystemHelpers + let(:credentials) { encryptables(:credentials1) } + let(:team) { teams(:team1) } + + it 'renders dashboard grid' do login_as_user(:alice) + visit("/encryptables/#{credentials.id}") + visit('/dashboard') expect(page.current_path).to eq('/dashboard') - expect(page).to have_selector('pzsh-hero', visible: true) expect(page).to have_selector('pzsh-banner', visible: true) + expect(page).to have_text('Recent Credentials', count: 1) expect(page).to have_text('Favourites', count: 1) expect(page).to have_text('Teams', count: 1) - expect(page).not_to have_selector 'div.content' - - expect(page).to have_selector('div.dashboard-grid-card', count: 3) + expect(page).to have_selector('div.primary-content', count: 3) end it 'navigates to team on team card click' do login_as_user(:alice) + visit("/encryptables/#{credentials.id}") + visit('/dashboard') + expect(page.current_path).to eq('/dashboard') expect(page).to have_selector('pzsh-hero', visible: true) - all('div.dashboard-grid-card').first.click - expect(page.current_path).to eq('/teams/235930340') + first('div.dashboard-grid-card', text: team.name).click + + expect(page.current_path).to eq("/teams/#{team.id}") expect(page).to have_selector 'div.content' end + + it 'lists recently accessed encryptable' do + login_as_user(:alice) + visit("/encryptables/#{credentials.id}") + visit('/dashboard') + + + expect(page.current_path).to eq('/dashboard') + + expect(page).to have_selector('pzsh-hero', visible: true) + + first('div.dashboard-grid-card', text: credentials.name).click + + expect(page.current_path).to eq("/encryptables/#{credentials.id}") + expect(page).to have_selector 'div.content' + end + end